__are_same
C++模板template的存在,为重载函数提供了很多方便。但有些时候我们要对不同的类型作不同的处理(比如整型与浮点型、实数与复数)。比如我要实现如下函数:
f
(
x
)
=
{
x
+
1
,
x
是
i
n
t
型
,
x
,
x
非
i
n
t
型
.
f(x)= \left\{ \begin{array}{ll} x+1 & ,x是int型, \\ x & ,x非int型. \end{array} \right.
f(x)={x+1x,x是int型,,x非int型.
那么,很显然我们希望写出这么一个模板函数:
template<typename T>
T f(T x){
// 如果 T 是 int 型 , 则 return x+1;
// 否则 return x;
}
问题在于,如何判断 T 是 int 型呢?( T == int 这样的表达式是不合法的)
先来看看解决方案,用到了内置的头文件 <ext/type_traits.h>,以及某个东西:
__are_same<T,int>::__value
#include<iostream>
#include <ext/type_traits.h>
using namespace std;
template<typename T>
T f(T x){
if (__are_same<T,int>::__value == true){
return x+1;
}// else
return x;
}
int main(){
cout << "f(5) = " << f(5) << endl; // 6
cout << "f(5.0) = " << f(5.0f) << endl; // 5
return 0;
}
从效果来看,很容易看懂这个东西判断 T 和 int 是否为同一类型,如果是,则 __value为true,否则 __value 为false.
C++还有类似的其它结构体,比如:
__is_integer<T>(判断是否为整数,如short,int,long,unsigned int…等)
__is_floating<T>(判断是否为浮点数,如float,double,long double等)
__is_pointer<T>(判断是否为指针类型,如int*,char*……等)
如果需求是找到判断类型的方法,那么到此为止这篇文章就已经够用了。如果还想知道其内部是如何实现的,那么请继续往下读。
有默认值的模板结构体
首先轻轻点击这个代码溯源到 __are_same的源代码。在我的电脑上,它位于C:\MinGW\lib\gcc\mingw32\9.2.0\include\c++\bits\cpp_type_traits.h. 这也是我们引用<ext/type_traits.h>的原因。
// Compare for equality of types.
template<typename, typename>
struct __are_same
{
enum { __value = 0 };
typedef __false_type __type;
};
template<typename _Tp>
struct __are_same<_Tp, _Tp>
{
enum { __value = 1 };
typedef __true_type __type;
};
这定义了两段 __are_same,可以看出它们都是结构体。但为什么有两段呢?
解释:
第一段是 __are_same 的默认构造。
第二段是 __are_same 的特别构造。即,如果传入的typename刚好符合第二段,那就用第二段来构造。
在代码上有个区别两者的细节,那就是:第一段 __are_same后面没有尖括号<>,但第二段有!
在此处,如果传入 __are_same 的两个类型刚好一样,那就满足第二段的构造,里面的 __value 就是1 (true) ;否则(类型不一样时)默认用第一段构造,__value 值为0.
其实我们也可以自定义一个玩玩。比如一个更实用的例子:实现 f ( x ) f(x) f(x)计算一个复数或实数 x x x的模长 ∣ x ∣ |x| ∣x∣。要求保持精度。
保持精度,即:如果传入 x x x 是 float 或 complex<float> ,返回值也是 float. 如果传入 x x x 是 double 或 complex<double>,返回值也是 double. 如果传入 x x x 是 long double 或 complex<long double>,则返回值为 long double.
也许你会觉得像下面这样复制六个函数,合计六行就搞定了——那这篇文章就白看了。如果要求的是矩阵范数这样复杂的东西,那么肯定不忍心复制六次。
#include<math.h>
#include<complex>
using namespace std;
float f(float x) { return abs(x);}
double f(double x) { return abs(x);}
long double f(long double x) { return abs(x);}
complex<float> f(complex<float> x) { return sqrt(real(x)*real(x)+imag(x)*imag(x));}
complex<double> f(complex<double> x) { return sqrt(real(x)*real(x)+imag(x)*imag(x));}
complex<long double> f(complex<long double> x){ return sqrt(real(x)*real(x)+imag(x)*imag(x));}
我们的应对策略如下:
创建一个 __typeHelper 结构体,其默认值 __is_complex = 0, 类型 __type 为输入的类型。
但是,当输入的东西刚好可以写成 complex<T>的形式,那么就进入第二段的构造,此时complex当中的类型才作为 __type!
#include<iostream>
#include<ext/type_traits.h>
#include<math.h>
#include<complex>
using namespace std;
template<typename T>
struct __typeHelper{
enum { __is_complex = 0};
typedef T __type;
};
template<typename T>
struct __typeHelper<complex<T>>{
enum { __is_complex = 1};
typedef T __type;
};
//求模长的函数 f
template<class T>
typename __typeHelper<T>::__type f(T x){
return sqrt(real(x)*real(x)+imag(x)*imag(x));
}
int main(){
cout << f(complex<double>(3.0,4.0)); // 5
return 0;
}
最后函数f,输入类型为 T,输出类型是根据结构体 __typeHelper<T>找到的——结构体内的 __type 类型就是输出类型。
有默认值的模板函数
既然存在「有默认值的模板结构体」,那么如法炮制可以写「有默认值的模板函数」。因此上述的求模长的函数也可以这么写:
// 默认的函数形式
template<typename T>
T f(T x){
return abs(x);
}
// 如果是 complex,则会自动采用如下函数
template<typename T>
T f(complex<T> x){
return sqrt(real(x)*real(x)+imag(x)*imag(x));
}
而对于第一例,
f
(
x
)
=
{
x
+
1
,
x
是
i
n
t
型
,
x
,
x
非
i
n
t
型
.
f(x)= \left\{ \begin{array}{ll} x+1 & ,x是int型, \\ x & ,x非int型. \end{array} \right.
f(x)={x+1x,x是int型,,x非int型.
其实现如下:
// 默认的函数形式
template<typename T>
T f(T x){
return x;
}
// 特别的函数形式
template<>
int f(int x){
return x+1;
}