查看原文
其他

想看懂stl代码,先搞定type_traits是关键

程序喵大人 程序喵大人 2022-08-22


type_traits在C++中是非常有用的技巧,可以说如果不懂type_traits,那根本看不懂stl相关代码,最近对type_traits比较感兴趣,于是找到了gcc的type_traits源码通读了一遍,总结一下。

type_traits称为类型萃取技术,主要用于编译期获取某一参数、某一变量、某一个类等等任何C++相关对象的类型,以及判断他们是否是某个类型,两个变量是否是同一类型,是否是标量、是否是引用、是否是指针、是左值还是右值,还可以将某个为某个类型去掉cv(const volitale)属性或者增加cv属性等等。


SFINAE

SFINAE技术,Substitution Failure is Not An Error,在编译期编译时,会将函数模板的形参替换为实参,如果替换失败编译器不会当作是个错误,直到找到那个最合适的特化版本,如果所有的模板版本都替换失败那编译器就会报错,以std::enable_if举个例子。

#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;

template <class T>
auto func(T t) -> std::enable_if_t<std::is_same<int, std::remove_cv_t<T>>::value, int>
{
   cout << "int" << endl;
}

template <class T>
auto func(T t) -> std::enable_if_t<std::is_same<double, std::remove_cv_t<T>>::value, double>
{
   cout << "double" << endl;
}

int main()
{
   int a = 1;
   double b = 2.9;
   func(a);
   func(b);

   // float c = 34.5;
   // func(c);

   return 0;
}

编译运行:g++ test.cc -std=c++14; ./a.out
int
double

注释部分的代码如果打开就会编译失败,代码中明确规定了func函数只接收类型为int和double的参数,向func中传入其它类型参数编译器则会报错。

通过SFINAE技术可以完成很多有趣的事,比如根据参数类型做不同的定制化操作。


type_traits原理

type_traits最核心的结构体应该就是integral_constant,它的源代码如下:

   template<typename _Tp, _Tp __v>
   struct integral_constant
   {
     static constexpr _Tp                  value = __v;
     typedef _Tp                           value_type;
     typedef integral_constant<_Tp, __v>   type;
     constexpr operator value_type() const noexcept { return value; }
     constexpr value_type operator()() const noexcept { return value; }
   };

   typedef integral_constant<bool, true>     true_type;
   typedef integral_constant<bool, false>    false_type;

基本上type_traits的每个功能都会使用到true_type和false_type,后续会介绍,这里先介绍代码中那两个operator函数的具体含义,operator type() const用于类型转换,而type operator()() const用于仿函数,见代码。

#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;

class Test
{
public:
   operator int() const
   {
       cout << "operator type const " << endl;
       return 1;
   }

   int operator()() const
   {
       cout << "operator()()" << endl;
       return 2;
   }
};

int main()
{
   Test t;
   int x(t);
   int xx = t;
   t();
   return 0;
}

编译运行:g++ test.cc; ./a.out
operator type const
operator type const
operator()()

还有个主要的模板是conditional

 template<bool, typename, typename>
   struct conditional;

 template<bool _Cond, typename _Iftrue, typename _Iffalse>
   struct conditional
   { typedef _Iftrue type; };

 // Partial specialization for false.
 template<typename _Iftrue, typename _Iffalse>
   struct conditional<false, _Iftrue, _Iffalse>
   { typedef _Iffalse type; };

当模板的第一个参数为true时type就是第二个参数的类型,当第一个参数为false时type就是第三个参数的类型,通过conditional可以构造出or and等功能,类似我们平时使用的带短路功能的|| &&,具体实现如下:

 template<bool, typename, typename>
   struct conditional;

 template<typename...>
   struct __or_;

 template<>
   struct __or_<>
   : public false_type
   { };

 template<typename _B1>
   struct __or_<_B1>
   : public _B1
   { };

 template<typename _B1, typename _B2>
   struct __or_<_B1, _B2>
   : public conditional<_B1::value, _B1, _B2>::type
   { };

 template<typename _B1, typename _B2, typename _B3, typename... _Bn>
   struct __or_<_B1, _B2, _B3, _Bn...>
   : public conditional<_B1::value, _B1, __or_<_B2, _B3, _Bn...>>::type
   { };

 template<typename... _Bn>
   struct disjunction
   : __or_<_Bn...>
   { };

通过disjunction可以实现析取功能,type为B1, B2, B…中第一个value为true的类型。

// cpp reference中的示例代码
struct Foo {
   template <class T>
   struct sfinae_unfriendly_check {
       static_assert(!std::is_same_v<T, double>);
   };

   template <class T>
   Foo(T, sfinae_unfriendly_check<T> = {});
};

template <class... Ts>
struct first_constructible {
   template <class T, class... Args>
   struct is_constructible_x : std::is_constructible<T, Args...> {
       using type = T;
   };
   struct fallback {
       static constexpr bool value = true;
       using type = void;  // type to return if nothing is found
   };

   template <class... Args>
   using with = typename std::disjunction<is_constructible_x<Ts, Args...>..., fallback>::type;
};

// OK, is_constructible<Foo, double> not instantiated
static_assert(std::is_same_v<first_constructible<std::string, int, Foo>::with<double>, int>);

static_assert(std::is_same_v<first_constructible<std::string, int>::with<>, std::string>);
static_assert(std::is_same_v<first_constructible<std::string, int>::with<const char*>, std::string>);
static_assert(std::is_same_v<first_constructible<std::string, int>::with<void*>, void>);

再看看conjunction的实现

 template<typename...>
   struct __and_;

 template<>
   struct __and_<>
   : public true_type
   { };

 template<typename _B1>
   struct __and_<_B1>
   : public _B1
   { };

 template<typename _B1, typename _B2>
   struct __and_<_B1, _B2>
   : public conditional<_B1::value, _B2, _B1>::type
   { };

 template<typename _B1, typename _B2, typename _B3, typename... _Bn>
   struct __and_<_B1, _B2, _B3, _Bn...>
   : public conditional<_B1::value, __and_<_B2, _B3, _Bn...>, _B1>::type
   { };

 template<typename... _Bn>
   struct conjunction
   : __and_<_Bn...>
   { };

通过conjunction可以判断函数的参数类型是否相同,代码如下:

#include <iostream>
#include <type_traits>

// func is enabled if all Ts... have the same type as T
template <typename T, typename... Ts>
std::enable_if_t<std::conjunction_v<std::is_same<T, Ts>...>> func(T, Ts...) {
   std::cout << "all types in pack are T\n";
}

// otherwise
template <typename T, typename... Ts>
std::enable_if_t<!std::conjunction_v<std::is_same<T, Ts>...>> func(T, Ts...) {
   std::cout << "not all types in pack are T\n";
}

int main() {
   func(1, 2, 3);
   func(1, 2, "hello!");
}

输出:
all types in pack are T
not all types in pack are T

再举一些平时用的很多的例子,还可以判断某个类型是否有const属性,添加去除某个类型的左值引用或者右值引用,添加去除某个类型的const或者volatile。

#include <iostream>
#include <type_traits>

int main()
{
   std::cout << std::boolalpha;
   std::cout << std::is_const<int>::value << '\n'; // false
   std::cout << std::is_const<const int>::value  << '\n'; // true
   std::cout << std::is_const<const int*>::value  << '\n'; // false
   std::cout << std::is_const<int* const>::value  << '\n'; // true
   std::cout << std::is_const<const int&>::value  << '\n'; // false
}

is_const实现很简单,就是利用模板匹配特性,其它的is_volatile等类似

 template<typename>
   struct is_const
   : public false_type { };

 template<typename _Tp>
   struct is_const<_Tp const>
   : public true_type { };

is_same的实现如下:

 template<typename, typename>
   struct is_same
   : public false_type { };

 template<typename _Tp>
   struct is_same<_Tp, _Tp>
   : public true_type { };

包括移除引用的功能, remove_reference

 /// remove_reference
 template<typename _Tp>
   struct remove_reference
   { typedef _Tp   type; };

 template<typename _Tp>
   struct remove_reference<_Tp&>
   { typedef _Tp   type; };

 template<typename _Tp>
   struct remove_reference<_Tp&&>
   { typedef _Tp   type; };

 // C++14之后这种xxx_t=xxx::type
 template<typename _Tp>
   using remove_reference_t = typename remove_reference<_Tp>::type;

add_const, add_volatile, add_cv等的实现

 template<typename _Tp>
   struct add_const
   { typedef _Tp const     type; };

 /// add_volatile
 template<typename _Tp>
   struct add_volatile
   { typedef _Tp volatile     type; };

 /// add_cv
 template<typename _Tp>
   struct add_cv
   {
     typedef typename
     add_const<typename add_volatile<_Tp>::type>::type     type;
   };

▼更多精彩推荐,请关注我们▼


代码精进之路


  代码精进之路,我们一起成长!



深入浅出虚拟内存
深入浅出虚拟内存(二)绘制虚拟内存排布图
深入浅出虚拟内存(三)堆内存分配及malloc实现原理
源码分析shared_ptr实现new[]和delete[]为何要配对使用?内存对齐
C++定时器的实现
C++线程池的实现



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存