目录
前言
模板特化大家都很熟了
template<typename T>
class Holder;
template<>
class Holder<TypeA> {};
template<>
class Holder<TypeB> {};
不过不知各位是否遇到过这种尴尬情况
typedef int TypeA;
typedef int TypeB;
看着很傻, 但是在 C艹 世界里还是很常见的, 比如对不同平台适配各种 int 类型的时候,
虽说有 stdint, 但要适配现有代码的时候还是难以避开这些问题
于是引出了本文的主题: 如何对一堆未知是否相同的类型做模板特化
strong typedef 法
这玩意儿在 boost 里面也有, 叫 BOOST_STRONG_TYPEDEF
大致原理是生成个新类型做包装
class WrapperA
{
public:
explicit WrapperA(const int) {...}
WrapperA(WrapperA const &) {...}
WrapperA(void) {...}
WrapperA &operator = (const int) {...}
WrapperA &operator = (WrapperA const &) {...}
operator const int &() const {...}
operator int &() {...}
bool operator == (WrapperA const &) const {...}
bool operator != (WrapperA const &) const {...}
bool operator < (WrapperA const &) const {...}
bool operator <= (WrapperA const &) const {...}
bool operator > (WrapperA const &) const {...}
bool operator >= (WrapperA const &) const {...}
private:
int v;
};
// WrapperB 同理, 完全重新定义一份
不过这玩意儿也不少缺点:
- 略微有点性能损失, 虽然大多数场景可以忽略
- 对于模板特化来说, 实际上 WrapperA 和 int 还是不一样的类型,
你得确保你的应用代码完全使用你的 wrapper 类型 -
对于需要算术运算的基本类型来说, 还有更多的麻烦事:
-
你得重载各种 operator 用来支持算数运算, 完整的大约得有 20 个左右,
而且还有各种类型运算后是需要返回新类型的 (比如 long 与 float 相乘结果是 double) -
你很卖力的重写完了所有 operator, 不出意外还会碰到各种 ambiguous
-
最终, 你发现使用 explicit 构造函数可以避免这种情况, 但是, 又发现以下最基本的初始化都不好用了:
BOOST_STRONG_TYPEDEF(unsigned int, my_uint) my_uint n = 0; // 这里 0 默认是 int, 然而由于 explicit 构造函数, 无法隐式转换 void func(my_uint n) {...} func(0); // 同理, 这边也无法隐式转换, 必须手动 func((my_uint)0);
-
总之, 这玩意儿不管怎样都无法完美的对应用层透明
-
模板递归法
这玩意儿是最近脑洞出来的结果, 不一定是最好方案, but it works ╮(╯▽╰)╭
首先, 我们先来熟悉下不是很常见的模板递归:
template<int N>
class Holder
{
public:
enum {value = Holder<N - 1>::value + 1};
};
template<>
class Holder<0>
{
public:
enum {value = 0};
};
int result = Holder<2>::value;
上述 result 的结果是 2, 实际上, 引用 Holder<2>
时会因为 Holder<N - 1>
而依次递归生成
Holder<2>
Holder<1>
Holder<0>
, 并最终停止于 Holder<0>
的特化版本
模板递归全部在编译期完成, 因此最终值本身可以作为编译期参数来参与更复杂的模板推导 (推倒什么的最讨厌了 @w@)
讲到这, 应该知道接下来的套路了吧? 用模板递归配合 int 类型模板参数特化, 来针对相同类型推导出不同模板
不过, 还剩个问题, int 类型的特化值哪来? 手动敲可不靠谱, 毕竟你并不知道到底在什么时候需要特化
于是, 还得引入个静态计数, 在编译期做一个自增的数值
这玩意儿有很多的实现方式, 有的编译器自己也有提供, 这里提供一个可以跨平台的版本 (出处找不着了, 求指正)
template<int N>
class _Counter
{
public:
char data[N + 1];
};
template<typename T_Type, int N, int Acc>
_Counter<Acc> _CounterSeen(T_Type, _Counter<N>, _Counter<Acc>);
template<typename T_Type>
class _CounterWrap
{
public:
static T_Type value(void);
};
#define CounterGet(...) \
(sizeof _CounterSeen(_CounterWrap<__VA_ARGS__>::value(), _Counter<1>(), _Counter< \
(sizeof _CounterSeen(_CounterWrap<__VA_ARGS__>::value(), _Counter<2>(), _Counter< \
(sizeof _CounterSeen(_CounterWrap<__VA_ARGS__>::value(), _Counter<4>(), _Counter< \
(sizeof _CounterSeen(_CounterWrap<__VA_ARGS__>::value(), _Counter<8>(), _Counter< \
(sizeof _CounterSeen(_CounterWrap<__VA_ARGS__>::value(), _Counter<16>(), _Counter< 0 \
>()).data - 1) \
>()).data - 1) \
>()).data - 1) \
>()).data - 1) \
>()).data - 1) // 需要更多的话, 这里可以继续添加
#define CounterInc(...) \
_Counter<CounterGet(__VA_ARGS__) + 1> _CounterSeen(__VA_ARGS__, \
_Counter<(CounterGet(__VA_ARGS__) + 1) & ~ CounterGet(__VA_ARGS__)>, \
_Counter<(CounterGet(__VA_ARGS__) + 1) & CounterGet(__VA_ARGS__)> \
);
// 用法
CounterInc(MyType)
char v0[CounterGet(MyType)]; // char [1]
CounterInc(MyType)
char v1[CounterGet(MyType)]; // char [2]
大约就是每次申明一个函数, 在这里面实例化了模板, 并最终导致 sizeof 计算的结果逐渐增大
好了, 万事俱备, 开始我们蛋疼的模板特化吧
// 原始版本
template<typename T, int T_Loop>
class Holder
{
public:
static void func(void)
{
Holder<T, T_Loop + 1>::func();
}
};
// 偏特化版本
template<typename T>
class Holder<T, 4>
{
public:
static void func(void)
{
defaultAction();
}
};
typedef int MyTypeA;
typedef int MyTypeB;
// 第一次特化
CounterInc(MyTypeA)
template<>
class Holder<MyTypeA, CounterGet(MyTypeA)>
{
public:
static void func(void)
{
// 特化版本
specializaionAction();
}
};
// 再来一次特化, 注意这里 MyTypeB 其实和 MyTypeA 完全相同
CounterInc(MyTypeB)
template<>
class Holder<MyTypeB, CounterGet(MyTypeB)>
{
public:
static void func(void)
{
// 特化版本
specializaionAction();
}
};
注意到其间的玄机了嘛
- 原始版本采用模板递归调用下一个版本, 如果没有提供任何特化, 将最终停止于偏特化版本
- 如果提供了特化, 由于 T_Loop 小于我们默认的偏特化版本, 所以优先被使用到
- 如果对同类型提供了多次特化, 那么首次特化的版本将被使用, 后续的版本相当于永远不会被使用到
注意事项
虽然这方法看上去很美, 但是依旧还是有坑的:
- 递归模板特化会严重消耗编译期的时间, 尤其模板如果需要对外的话, 是需要完全在头文件的, 代码污染性比较严重, 建议谨慎使用
- Windows 平台, 该方法非常容易造成 C1001 (大约就是编译器内部堆栈爆了), 也可能搞出各种诡异问题
转载请注明来自: http://zsaber.com/blog/p/139
既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)