建设商业门户网站的重要性,绿岛网,兰州网站建设加q.479185700,长沙百姓网招聘类型萃取类型判断typeiddecltype和declvalenable_if 类型萃取
通过type_traits可以实现在编译期计算、查询、判断、转换和选择#xff0c;增强了泛型编程的能力#xff0c;也增强了我们程序的弹性#xff0c;让我们能够在编译期就能够优化改进甚至排错#xff0c;进一步提…类型萃取类型判断typeiddecltype和declvalenable_if 类型萃取
通过type_traits可以实现在编译期计算、查询、判断、转换和选择增强了泛型编程的能力也增强了我们程序的弹性让我们能够在编译期就能够优化改进甚至排错进一步提高代码质量。 头文件 #include 类型判断
type_trits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类在很多场合中会使用到这些特性。
type_trits的类型选择功能在一定程度上可以消除冗长的switch-case或者if-else的语句降低程序的复杂程度。
这些类型判断的方法从std::integral_constant派生用来检查模板类型是否为某种类型通过这些trait可以获取编译期检查的bool值结果。
下面的表格是一些常用的判断类型traits。更过从网址 点击获取。
traits类型说明templatestruct is_void;T是否为void类型templatestruct is_floating_point;T是否为浮点类型templatestruct is_array;T是否为数组类型templatestruct is_pointer;T是否为指针类型包括函数指针但不包括成员函数指针templatestruct is_enum;T是否为枚举类型templatestruct is_union;T是否为非union的class/struct类型templatestruct is_class;T是否为类类型而不是union类型templatestruct is_funtion;T是否为函数类型templatestruct is_reference;T是否为引用类型左值引用或者右值引用templatestruct is_arithmetic;T是否为整型和浮点类型templatestruct is_fundamental;T是否为整型、浮点、void、或nullptr_t类型templatestruct is_object;T是否为一个对象类型不是函数、不是引用、不是voidtemplatestruct is_scalar;T是否为arithmetic、enumeration、pointer、pointer to member或std::nullptr_t类型templatestruct is_compound;T是否非fundamental类型构造的templatestruct is_member_pointer;T是否为成员函数指针类型templatestruct is_polymorphic;T是否有虚函数templatestruct is_abstract;T是否为抽象类templatestruct is_signed;T是否是有符号类型templatestruct is_unsigned;T是否是无符号类型templatestruct is_const;T是否为const修饰的类型
使用方法
#include iostream
#include type_traitsint main()
{std::cout is_const: std::endl;std::cout int: std::is_constint::value std::endl;std::cout const int: std::is_constconst int::value std::endl;return 0;
}输出结果为 is_const:
int: 0
const int: 1判断类型的traits一般和std::enable_if结合起来使用,通过SFINAE特性来实现功能更强大的重载。后面会讲到。
判断两个类型之间的关系traits
traits说明templatestruct is_same;判断两个类型是否相同templatestruct is_base_of;判断Base类型是否为Derived类型的基类templatestruct is_convertible;判断前面的模板参数类型能否转换为后面的模板参数类型
简单介绍一下is_same的用法
#include iostream
#include type_traitsint main()
{std::cout int: std::is_sameint, int::value std::endl;//这里使用了decltype可以获取变量的类型为intstd::cout int: std::is_samedecltype(a), int::value std::endl;std::cout const int: std::is_sameint, unsigned int::value std::endl;return 0;
}输出结果为
int: 1
int: 1
const int: 0类型的转换traits
常用的类型转换traits包括对const的修改—-const的移除和添加引用的修改—–引用的移除和添加数组的修改和指针的修改。
下表为类型转换的方法
traits说明templatestruct remove_const;移除consttemplatestruct add_const;添加consttemplatestruct remove_reference;移除引用templatestruct add_lvalue_reference;添加左值引用templatestruct add_rvalue_reference;添加右值引用templatestruct remove_extents;移除数组顶层的维度templatestruct remove_all_extents;移除数组所有的维度templatestruct remove_pointer;移除指针templatestruct add_pointer;添加指针templatestruct decay;移除cv或添加指针templatestruct common_type;获取公共类型
简单介绍一下使用方法 具体可以参考c11深入理解93页。 #include iostream
#include type_traitsint main()
{std::cout int: std::is_sameint, add_constint::value std::endl;return 0;
}输出结果为
int: 0typeid 包含头文件 #include 在讲解typeid神秘面纱之前我们先了解一下RTTI(Run-Time Type Identification),中文为运行时类型识别它使程序能够获取由基指针或引用所指向的对象的实际派生类型。即允许 “用指向基类的指针或引用来操作对象” 的程序能够获取到 “这些指针或引用所指对象” 的实际派生类型。
在C中为了支持RTTI提供了两个操作符dynamic_cast和typeid。
dynamic_cast允许运行时刻进行类型转换从而使程序能够在一个类层次结构中安全地转化类型与之相对应的还有一个非安全的转换操作符static_cast因为这不是本文的讨论重点所以这里不再详述感兴趣的可以自行查阅资料。typeid是C的关键字之一等同于sizeof这类的操作符。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。
我们来看一下如何使用
#include typeinfostruct Base { virtual ~Base() default; };
struct Derived : Base {};int main()
{Base b1;Derived d1;const Base *pb b1;std::cout typeid(*pb).name() \n;pb d1;std::cout typeid(*pb).name() \n;std::cout typeid(1).name() \n;std::cout typeid(2.444).name() \n;return 0;
}输出结果
4Base
7Derived
i
d上面是在gcc编译上编译的结果与vc,clang都大不相同。
decltype和declval
有时候要获取函数的返回类型是一件比较困难的事情 比如下面代码
template typename F, typename Arg
?? func(F f, Arg arg)
{return f * arg;
}
由于函数的入参都是两个模板参数导致我们不能直接确定返回类型那么我们可以通过decltype来推断函数返回类型。
template typename F, typename Arg
decltype((*(F*)0)*((*(Arg*)0))) func(F f, Arg arg)
{return f * arg;
}
上面的比较繁琐所以我们可以使用返回类型后置去简化。
template typename F, typename Arg
auto func(F f, Arg arg)-decltype(f * arg )
{return f * arg;
}
这样看起来就舒服多了。
但是有些时候我们不能通过decltype来获取类型了如下面
#include type_traitsclass A
{A()delete;
public:int operator() ( int i ){return i;}
};int main()
{int a A()(3);decltype( A()(0) ) i 4;std::cout i std::endl;std::cout a std::endl; //输出结果为3return 0;
}
上面的代码将会编译报错因为A没有默认构造函数对于这种没有默认构造函数的类型我们如果希望能推导其成员函数的返回类型则需要借助std::declval。
修改为
decltype( std::declvalA()(std::declvalint())) i 4;
上面的代码可以通过因为std::declval能够获取任何类型的临时值不管它有没有默认构造函数。因为我们通过declval()获取了A的临时对象。需要注意一点declval获取的临时值不能用于求值因此必须使用decltype来推断出最终的返回类型。
其实上面做了这么多还是比较麻烦C11提供了另外一个trait——std::result_of用来在编译期获取一个可调用对象的返回类型。
上面的代码改写如下
std::result_ofA(int)::type i 4;
这段代码实际上等价于 decltype( std::declval()(std::declval()))。
enable_if
在讲enable_if之前我们先来了解什么是SFINAE,它是Substitution failure is not an error 的首字母缩写。
我们通过一个例子来了解一下SFINAE机制
templatetypename T
void Fun(T *t)
{*t * 1;
}templatetypename T
void Fun(T t)
{t 1;
}int main()
{Fun(1);return 0;
}
上面运行的时候将会匹配到第二个重载函数在匹配的过程中当匹配到void Fun(T t)时将一个非0的整数来替换T 是错误的此时编译器并不会报错此时就叫failure,然后继续匹配其他的重载函数如果最后发现void Fun(T t)能匹配上整个过程就不会报错如果匹配不到就会报error这就是为什么叫Substitution failure is not an error。
这个规则就叫SFINAE。
std::enable_if利用SFINAE实现根据条件选择重载函数std::enable_if的原型如下
templatebool B, class T void
struct enable_if;
简单介绍一下使用的方法
//is_arithmetic为判断是否为整型和浮点类型的traits
//这里在使用的时候需要加上typename在enable_if前面
//是要告诉编译器后面的标识符是一个类型名来处理否则会被编译器当做静态变量处理
templateclass T
typename std::enable_ifstd::is_arithmeticT::value, T::type foo(T t)
{return t;
}int main()
{auto r foo(1);auto r1 foo(1.2);std::cout r std::endl; //1整数std::cout r1 std::endl; //1.2,浮点数//auto r2 foo(test); //编译错误return 0;
}上面的函数模板通过enable_if做了限定只能接受整型和浮点型我们来看一下foo(1)运行步骤
1. 根据传入的实参1推断出T为int类型
2. std::is_arithmeticT::value变为std::is_arithmeticint::value此时返回值为true
3. std::enable_ifstd::is_arithmeticT::value, T中的最后一个T为int通过::type获取类型。
4. foo函数的返回值被确定为int类型