1.type_traits-类型萃取
(1)type_traits可以在一定程度上消除 switch-case 或者 if-else语句,降低程序的复杂度
(2)可以在编译期就检查出是否是正确类型
1.1基本的type_traits
定义编译期常量
struct GetLeftSize
{
static const int value =1;
}
或
struct GetLeftSize
{
enum { value = 1};
};
在c++11中定义编译期常量,无须自己定义static const int 或 enum类型,只需要从std::integral_constant派生:
template<typename Type>
struct GetLeftSize : std::integral_constant<int, 1>
{
};
可以通过 GetLeftSize::value来获取常量1,看下integral_constant的包装:
template<class T, T v>
struct integral_constant
{
static const T value = v;
typedef T value_type;
typedef integral_constant<T, v> type;
operator vaue_type() {return value;}
};
而true_type和false_type是integtal_constant的一个实例:
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
std::true_type和std::false_type分别定义了编译期的true和false类型
1.2类型判断的type_traits
template<class T>
struct is_integral;
检查T类型: bool char char16_t char32_t wchar_t short int long longlong,其中一种返回true。std::is_integral::value为true
is_void
is_floating_point 浮点类型(非指针)
is_array
is_pointer 指针类型(包括函数指针,不包括成员函数指针)
is_enum
is_union 是否为非union的class/struct
is_class
is_function 是否为函数类型
is_reference 是否为引用类型(左右引用都行)
is_arithmetic是否为整型和浮点型
is_fundamental 是否为整型、浮点、void、nullptr_t
is_object 是否为一个对象类型(不是函数 不是引用 不是void)
is_member_pointer 是否为成员函数指针类型
is_polymorphic 虚函数
is_abstract 抽象类
is_signed 有符号类型
is_unsigned 无符号类型
is_const 为const修饰的类型
通过 std::is_xxx::value来判断是否满足条件
std::is_const<const int&>::value == true;
std::is_const<const int* >::value == false;
1.3 traits之判断两个类型的关系
template<class T, class U>
struct is_same; 判断两个类型是否相同
template<class Base, class Derived>
struct is_base_of; //判断Base是否为Derived的基类
template<class From, class To>
struct is_convertible; 判断前面类型是否可以转换为后面类型
#include<iostream>
#include<type_traits>
class A {};
class B : public A{};
class C{};
int mian()
{
bool b2a = std::is_convertible<B*, A*>::value; // true
bool a2b = std::is_convertible<A*, B*>::value; // false 不能向下转换
........
}
3.type_traits之 类型的转换
template<class T>
struct remove_const; 移除const
以下写法省略template<class T> struct
add_const
remove_reference
add_lvalue_reference
add_rvalue_reference
remove_extent // 移除数组顶层的维度
remove_all_extent //移除数组的所有维度
remove_pointer 移除指针
add_pointer 添加指针
decay 移除cv或添加指针
common_type 获取公共类型
获取转换后的类型是通过:
std::xx<>::type来获取类型的,非::value
根据模板参数类创建对象时,要注意移除引用:
template<typename T>
typename std::remove_reference<T>::type* Create()
{
typedef typename std::remove_reference<T>::type U;
return new U();
}
在上述例子中,返回值和函数中typename是因为模板的嵌套从属类型,好好看模板基础吧!哈哈哈~~~~
模板参数类型可能是引用类型,而创建对象时,需要原始的类型,不能用引用类型,所以需要将可能的引用移除
移除cv引用类型(什么鬼东西,从来没有见到过,为了满足下荣誉感,还加上吧)
template<typename T>
T* Create()
{
return new T();
}
int* p = Create<const volatile int&>();
编译失败;需移除引用和cv符才能获取原始的类型int
template<typename T>
typename std::remove_cv<typename std::remove_reference<T>::type>::type* Create()
{
typedef typename std::remove_cv<typename std::remove_reference<T>::type>::type U;
return new U();
}
代码较长,用decay来替代去除引用和cv(麻蛋,什么鬼,前面不是只说decay能去处cv,怎么又多出个引用啊,~~~):
template<typename T>
typename std::decay<T>::type* Create()
{
typedef typename std::decay<T>::type U;
return new U;
}
cv : const volatile
decay的功能还不止为此,还可以用于数组和函数:
先移除T类型的引用,得到类型U,U定义为remove_reference<T>::type
如果is_array<U>::value为true,修改类型type为remove_extent<U>::type*;
否则如果is_function<U>::value为true,修改类型为add_pointer<U>::type
否则修改类型为remove_cv<U>::type,去处const 或 volatile
(上面的法则真是让人想吐,那个SB定义的!!)
int&& ---int
const int& -- int
int[2] -- int*
int(int) -- int(*)(int)
函数变成函数指针可以保存下来(但是函数变成函数指针到底指的是什么鬼?就是类似int(int)变成int(*)(int)吗?)
using FnType = typename std::decay<F>::type 实现函数指针类型的定义
1.2 traits -- 根据条件选择
std::conditional 在编译期根据一个判断式选择两个类型中的一个:
template<bool B, class T, class F>
struct conditional;
如果B为true,则conditional::type为T,否则为F
比较两个类型输出较大的那个:
typedef std::conditional<(sizeof(long long) > sizeof(long double)), long long, long double>::type max_size_t;
count<<typeid(max_size_t).name()<<endl;
1.3 traits--获取可调用对象返回类型
template<typename F, typename Arg>
?? Func(F f, Arg arg)
{
return f(r);
}
不能直接确定返回的类型;可以通过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_traits>
class A
{
A() = delete;
public:
int operator()(int i)
{
return i;
}
};
int main ()
{
decltype(A()(0)) i =4;
cout<<i<<endl;
return 0;
}
上述的代码会报错,因为A没有默认构造函数;对于没有默认构造函数的类型,如果希望推导其成员函数的返回类型,需要借助std::declval
decltype(std::declval<A>()(std::declval<int>())) i =4;
std::declval能获取任何类型的临时值,不管它是否有默认构造函数,因此,可以通过declval<A>()来获取A的临时值;
注:declval获取的临时值引用不能用于求值,因此,需要用decltype来推断出最终的返回类型
c++11提供了另外一个trait函数,用来获取一个可调用对象,std::result_of,这个真的很吊的!!!!!!!!!!!!!
std::result_of<A(int)>::type i = 4;
其实,std::result_of<A(int)>::type 实际上等价于 decltype(std::declval<A>()(std::declval<int>())) 这个真不好用,输入都费劲!!!
std::result_of的原型如下:
tempalte<class F, class...ArgTypes>
class result_of<F(ArgTypes...)>;
std::result_of<Fn(ArgTypes...)>要求Fn为一个可调用对象,不能是一个函数类型,函数类型不是一个可调用对象。
typedef std::result_of<decltype(fn)(int)>::type A; // 这种方式是错误的,这是一个函数而非可调用对象
这里说下函数对象的定义:
1.函数指针
2.具有operator()成员函数的类对象(仿函数)
3.可被转换为函数指针的类对象
4.类成员(函数)指针
例子就不举了,反正你也看不懂,哈哈哈~~~~~~~
result_of例子走起,真尼玛牛逼,,,,,但是一般类没有构造函数的情况比较少,那么是不是用decltype比较好些,不需要转化为可调用对象
example1:
int fn(int) { return int(); } 函数
如果要对某个函数使用std::result_of,首先将函数转化为可调用对象
typedef std::result_of<decltype(fn)& (int)> ::type A; // 这是什么鬼啊啊???
typedef std::result_of<decltype(fn)* (int)>::type B; // 这个也看不懂????
typedef std::result_of<typename std::decay<decltype(fn)>::type(int)> ::type C;
static_assert(std::is_same<A, B>::value, "not equal"); true
static_assert(std::is_same<A, C>::value, "not equal"); true
static_assert(std::is_same<B, C>::value, "not equal"); true
example2:
template<typename Fn>
auto GroupBy(const vector<Person>& vt, const Fn& keySelector)-> multimap( decltype(keySelector((Person& ) nullptr)), Person>
{
typedef decltype(keySelector(*(Person*) nullptr)) key_type;
multimap<key_type, Person> map;
std::for_each(vt.begin(), vt.end(), [&](const Person& person)
{
map.insert(make_pair(keySelector(person), person));
});
return map;
}
可以看出键值是以Fn作为函数,Person对象作为输入参数来获取类型的。
但是decltype(keySelector(*(Person*) nullptr)) key_type 比较难懂啊??????ri
用result_of来替换
template<typename Fn>
multimap<typename std::result_of<Fn(Person)>::type, Person>
GroupBy(const vector<Person>& vt, Fn&& keySelector)
{
typedef std::result_of<Fn(Person)>:: type key_type; // Fn(Person)两个里面都是类型,到底是参数还是都行????
multimap<key_type, Person> map;
std::for_each(vt.begin(), vt.end(), [&] (const Person& person)
{
map.insert(make_pair(keySelector(person), person));
});
return map;
}
1.4 traits--根据条件禁用或启用
匹配重载函数:
template<typename T>
void Fuc(T*){ }
template<typename T>
void Fun(T){}
int main()
{
Func(1);
return 0;
}
将会匹配第二个重载函数
template<bool B, class T = void>
struct enable_if;
在判断条件B为true时,enable_if才有效,否则的话编译失败
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)
{
return t;
}
auto r = foo(1);
auto r1 = foo(1.2);
auto r2 = foo("tess"); // 编译错误
std::enable_if 不仅可以作用于返回值,还可以作用于模板定义、类模板特化、入参类型的限定
// 判断第二个入参类型是否为integral类型(我感觉这句话有问题,因为判断T是否为整型,T也是第一个参数类型啊?你说是不是?)
template<calss T>
T foo2(T t, typename std::enable_if<std::is_integral<T>::value, int>::type = 0) // 默认值为0
{
return t;
}
foo2(1,2); 可以
foo2(1," ");第二个参数错误
// 对模板参数T做了限定,T只能为integral类型
template<class T, class = typename std::enable_if<std::is_integral<T>::value>::type >
T foo3(T t)
{
return t;
}
template<class T, class Enable = void>
class A;
// 模板特化,对模板参数做了限定,模板参数只能为浮点型
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type>{};
A<double> a;
A<int> a; // 编译错误,模板参数应该为浮点型
要求:对于入参为arithmetic类型的返回 0 ,非arithmetic类型的返回1;
template<class T>
typename enable_if<std::is_arithmetic<T>::value, int> ::type foo(T t)
{
cout<<t <<endl;
return 0;
}
template<class T>
typename enable_if<!std::is_arithmetic<T>::value, int>::type foo(T& t)
{
cout<<typeid(T).name()<<endl;
return 1;
}
std::enable_if的第二个参数是默认模板参数void类型,因此在函数没有返回值时,后面的模板参数可以省略
typename std::enable_if<std::is_arithmetic<T>::value>::type fool(T t) //返回值为void, 前面是为了检查类型是否符合条件
{
cout<<typeid(T).name()<<endl;
}
typename std::enable_if<std::is_same<T, std::string>::value>::type foo1(T& t)
{
cout<<typeid(T).name()<<endl;
}
可以看到std::enable_if的强大重载机制,即使返回值相同也可以重载,(NIMA当我瞎啊,入参类型不是不一样吗?引用到底影不影响啊?????)
下面来一段牛逼的代码:
template<typename T>
string ToString(T t)
{
if(typeid(T) == typeid(int) || typeid(T) == typeid(double) || typeid(T) == typeid(float) )
{
std::string stream ss;
ss<<value; // 不要问我,这句话我也看不懂??????????????
return ss.str();
}
else if(typeid(T) == typeid(string))
{
return t;
}
}
用enable_if来替换:
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, string>::type
ToString(T& t) { return std::to_string(t); }
tempalte<class T>
typename std::enable_if<std::is_same<T, std::string>::value, string>::type
ToString(T& t) { return t; }