c++11 之type_traits

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luoqie123/article/details/52026467

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; }



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页