模板函数限制参数为特定类型

问题

最近要写一个模板函数,要求是必须被指定的类型才能执行,未被指定的类型要求报错。

注册器模板类

其实所谓接受指定类型作为参数就是对类型进行注册,凡是注册过的类型才能传入模板,非注册类型应当匹配失败,所以直接使用一个模板类的特化来实现注册。

template <typename T>
struct Register{};
template <>
struct Register<myint> {
  typedef myint Type;
  constexpr static const char* TypeName = "myint";
};
template <>
struct Register<mylong> {
  typedef mylong Type;
  constexpr static const char* TypeName = "mylong";
};
template <>
struct Register<float> {
  typedef float Type;
  constexpr static const char* TypeName = "float";
};

这里普通类是空的没有任何成员,而凡是特化的类都定义了对应的特化类型成员。下面的const char*只是为了检查用的,可有可无。
这样我们相当于注册了三个类型进入注册器,然后就可以利用这个注册器来完成我们需要的模板函数。

模板函数实现

template <typename T, typename = typename Register<T>::Type>
void print(const T& x) {
  cout<<Register<T>::TypeName<<'\t'<<x<<'\n';
}

这个模板函数有两个模板参数,分别是T和Register::Type类型,如果Register::Type存在则模板匹配成功,如果不存在则模板匹配失败,由于我们的注册器默认是空,不存在Type类型,所以未被特化的类型必然匹配失败,从而实现了只允许特定类型传入函数。
测试一下

int main() {
  print(-1);
  print((long long int)(2147483650));
  print(12.11f);
  return 0;
}

可以正常编译允许,运行结果为

myint	-1
mylong	2147483650
float	12.11

而如果我们把12.11f中的f去掉编译就会保存,提示没有print(const double&)的匹配函数。可以看到通过这种方法可以实现类型注册机制,每当我们需要添加一个新类型时只需要添加一个Register的特化即可,这种方法还有一个好处是如果我们的函数(这里的print)如果还要根据传入类型不同做区分操作,我们只需要在特化类中添加一个static的函数即可实现不同传入类型的区别操作。有点类似我在Register特化时加入的const char*变量实现了不同类型返回各自类型的类型名。

对vector和initializer_list特殊操作

如果当我注册了类型T之后,我希望我的函数还可以接收vector和{T(t1),T(t2)}这种初始化序列并且执行对应操作,那么我们只需要添加如下两个函数即可

template <typename T, typename = typename Register<T>::Type>
void print(const vector<T>& x) {
  cout<<"v_"<<Register<T>::TypeName<<'\t';
  for(const auto& i:x)
  cout<<i<<'\t';
  cout<<endl;
}
template <typename T, typename = typename Register<T>::Type>
void print(const initializer_list<T>& x) {
  cout<<"v_"<<Register<T>::TypeName<<'\t';
  for(const auto& i:u)
  cout<<i<<'\t';
  cout<<endl;
}

测试一下

int main() {
  print(vector<float>({12.11,9.9}));
  print({1,2,3,4});
  return 0;
}

运行结果为

v_float	12.11	9.9	
v_myint	1	2	3	4	

可以看到同样可以得到正确的结果。

邪道

上面是正常的一种实现方法,比较容易理解,而且可以根据注册类型不同实现函数细节处理的区别,就好像前面cout的时候可以输出不同的参数类型名称。如果仅仅只想要注册类型限制函数传入的参数类型,那么还有下面这种邪道做法,可以做很小的修改实现新类型注册,不过由于模板我本来也只是刚刚开始学习,因此这个实现只是一个原型,仅仅实现了功能并不算特别好用。

template <typename... T>
struct Regist{};

template <typename U,typename... T>
struct isRegister {};
template <typename U,typename t,typename... T>
struct isRegister<U,t,T...>{
  enum {value = is_same<t,U>::value|| isRegister<U,T...>::value};
};
template <typename U>
struct isRegister<U> {enum {value=0};};

这里定义了两个类Regist和isRegister其中Regist没有意义,仅仅用来传递模板参数,isRegister通过递归的方法遍历变长模板参数,找到变长模板参数中是否有可以匹配到的类型,有则返回1,没有则返回0。
然后就是定义一个邪道模板函数了。

template <typename T,typename... U>
bool xiedao(const T& a,const Regist<U...>& b) {
  return isRegister<T,U...>::value;
};

这个函数接收两个参数,一个是我们希望判断是否被注册的类型的变量,另一个是我们前面定义的用来传递模板参数的Regist类型的实例,由于Regist是空的,其实例没有任何意义,有意义的仅是其实例化时其类型的模板参数。typedef可以完成一个注册表的定义,然后利用这个注册表和xiedao函数就可以完成类型注册匹配了。具体使用方法如下

typedef Regist<int,float,mylong> Reg;

int main() {
  typedef Regist<int,float,mylong> Reg;
  cout<<"xie dao cheng gong le ma?"<<xiedao(int(),Reg())<<'\n';
  cout<<"xie dao cheng gong le ma?"<<xiedao(float(),Reg())<<'\n';
  cout<<"xie dao cheng gong le ma?"<<xiedao(mylong(),Reg())<<'\n';
  cout<<"xie dao cheng gong le ma?"<<xiedao(double(),Reg())<<'\n';
  return 0;
  }

运行结果如下

xie dao cheng gong le ma?1
xie dao cheng gong le ma?1
xie dao cheng gong le ma?1
xie dao cheng gong le ma?0

可以看到我们注册了int,float,mylong三个类型,其中int、float和mylong都成功匹配了,double匹配失败,完成了我们的功能。这样我们需要注册什么类型只需要在typedef的注册表中添加进去即可。

后来通过尝试对上面的实现做了些许改进,现在用起来更加自然了。

template <typename... T>
struct Regist{};

template <typename U,typename T>
struct isRegister {};
template <typename U,typename t,typename... T>
struct isRegister<U,Regist<t,T...>>{
  enum {value = is_same<t,U>::value|| isRegister<U,Regist<T...>>::value};
};
template <typename U>
struct isRegister<U,Regist<>> {enum {value=0};};

这里就不再需要构造临时的对象来传递模板参数了,直接把Regist作为模板参数传入isRegister即可。然后同样递归匹配注册类型。这时的使用就显得更加自然。

int main() {
  typedef Regist<int,float,mylong> Reg;
  cout<<isRegister<int,Reg>::value<<'\n';
  cout<<isRegister<float,Reg>::value<<'\n';
  cout<<isRegister<mylong,Reg>::value<<'\n';
  cout<<isRegister<double,Reg>::value<<'\n';
  return 0;
}

运行结果如下

1
1
1
0

这样就可以配合enable_if实现编译期的匹配判定。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值