现代C++模板与泛型编程_第4章_remove_all_sequence,integer_sequence,is_union

本文继续介绍第四章标准库的典型内容之remove_all_extents, std::integer_sequence, std::is_union等。

文章目录

  • 第4章内容
    • [4.7 remove_all_extents (C++11)](#4.7 remove_all_extents (C++11))
      • [4.7.1 作用测试](#4.7.1 作用测试)
      • [4.7.2 源码解析](#4.7.2 源码解析)
      • [4.7.3 理解未知大小的数组](#4.7.3 理解未知大小的数组)
      • [4.7.4 typename关键字在模板中的必要性](#4.7.4 typename关键字在模板中的必要性)
      • [4.7.5 过程理解](#4.7.5 过程理解)
    • [4.8 std::integer_sequence(C++14)](#4.8 std::integer_sequence(C++14))
      • [4.8.1 std::integer_sequence的基本使用](#4.8.1 std::integer_sequence的基本使用)
      • [4.8.2 源码分析](#4.8.2 源码分析)
      • [4.8.3 std::make_integer_sequence (C++14)作用](#4.8.3 std::make_integer_sequence (C++14)作用)
      • [4.8.4 std::make_index_sequence (C++14)](#4.8.4 std::make_index_sequence (C++14))
      • [4.8.5 实现 make_integer_sequence<int,5>](#4.8.5 实现 make_integer_sequence<int,5>)
      • [4.8.6 实现逆向Integer_Sequence](#4.8.6 实现逆向Integer_Sequence)
      • [4.8.7 将一个数字重复多次生成一个类型Repeat_Integer](#4.8.7 将一个数字重复多次生成一个类型Repeat_Integer)
    • [4.9 std::is_union、is_class 、std::integral_constant](#4.9 std::is_union、is_class 、std::integral_constant)
      • [4.9.1 std::is_union 类模板(C++11)](#4.9.1 std::is_union 类模板(C++11))
      • [4.9.2 std::is_class 类模板](#4.9.2 std::is_class 类模板)
      • [4.9.3 integral_constant](#4.9.3 integral_constant)

第4章内容

4.7 remove_all_extents (C++11)

4.7.1 作用测试

remove_all_extents 是C++11中引入的类模板;

作用:将一个数组中的数组类型部分移除掉,只保留元素类型。

cpp 复制代码
  void test()
  {
      int a[12];   // a的类型 = int[12],
      float f[12]; // f的类型 = float[12],
      int b[5][6]; // b的类型 = int[5][6]
      /*
          对类型的理解:
          1 决定的内存的大小,比如类型是 int[12],占用了 sizeof(int)*12个小大内存
      */
      cout << typeid(std::remove_all_extents<decltype(a)>::type).name() << endl;
      // int 
      cout << typeid(std::remove_all_extents<decltype(b)>::type).name() << endl;
      // int
  }

4.7.2 源码解析

remove_all_extents的源码如下:

cpp 复制代码
    template <typename T>
    struct remove_all_extents
    {
        using type = T;
    };
    // 特化,T[] 表示一个未知大小的数组类型,处理不定长数组情况
    template <typename T>
    struct remove_all_extents<T[]>  // 这是
    {
        using type = typename remove_all_extents<T>::type;
};
// 特化 T[N]
    template <typename T, std::size_t N>
    struct remove_all_extents<T[N]>
    {
        using type = typename remove_all_extents<T>::type;
    };

4.7.3 理解未知大小的数组

T[]的匹配情况:

情况1:函数参数

cpp 复制代码
    void func1(int arr[])  // 等价 int *arr
    {

    }
    void func2(int matrix[][5])  // int (*matrix)[5],指向5个int的指针
    {

    }

情况2:外部声明,这在什么情况下使用?

cpp 复制代码
    extern int ext_array[];  //
    /*
        使用:在 .h中声明 
            extern int ext_array[]; 
        在.cpp中定义:
            int ext_array[4]; 
*/

情况3:模板特化匹配

cpp 复制代码
    void test()
    {
        // 或者用于演示类型的示例:
        using dynamic_array_t = int[];  // 这是一个类型,用于模板匹配
        // 测试remove_all_extents模板
        using base_type1 = typename sp2::remove_all_extents<int[5]>::type;        // int
        using base_type2 = typename sp2::remove_all_extents<int[5][10]>::type;    // int
        using base_type3 = typename sp2::remove_all_extents<dynamic_array_t>::type; // int

        std::cout << "类型测试: "
            << typeid(base_type1).name() << ", "
            << typeid(base_type2).name() << ", "
            << typeid(base_type3).name() << std::endl;
    }

4.7.4 typename关键字在模板中的必要性

cpp 复制代码
typename remove_all_extents<T>::type; 这样定义

默认情况下,访问一个类中的静态成员都是通过 remove_all_extents::type; 编译器默认这样方式访问的静态成员。

为了区分开,告诉编译器这不是访问一个静态变量,而是要访问一个类型,而不是静态成员。需要依赖 typename的情况:

情况1:必须使用typename情况

cpp 复制代码
    struct A {
        static int type; // type 是一个静态成员变量
    };

    struct B {
        using type = int; // type 是一个类型别名
    };

    template<typename T>
    void func() 
    {
         // 这里必须使用 typename,表示type是一个类型,而不是静态变量
         typename T::type x; 
         // 歧义:这是声明了一个名为 x 的 T::type 类型的变量,
        // 还是将 T::type 作为静态变量乘以变量 x?
    }

    void test()
    {
        func<B>();
}

4.7.5 过程理解

在源码中加入调试代码,每次调用都输出 log()

cpp 复制代码
	template<typename T>
	struct Remove_all_extents
	{
		using type = T; // 默认情况下,类型不变

		// 添加静态函数,用于测试
		static void log()
		{
			cout << "调用了泛化 Remove_all_extents<T>" << endl;
		}
	};

	// 特化 T[] 
	template<typename T>
	struct Remove_all_extents<T[]>
	{
		using type = typename Remove_all_extents<T>::type; // 递归处理动态数组
		// 添加静态函数,用于测试
		static void log()
		{
			cout << "调用了动态数组特化 remove_all_extents<T[]>" << endl;
			// 递归记录
			Remove_all_extents<T>::log();
		}
	};

	// 特化 T[N]
	template<typename T, std::size_t N>
	struct Remove_all_extents<T[N]>
	{
		using type = typename Remove_all_extents<T>::type; // 递归处理静态数组
		// 添加静态函数,用于测试
		static void log()
		{
			std::cout << "调用了特化版本 Remove_all_extents<T[" << N << "]>" << std::endl;
			// 递归记录
			Remove_all_extents<T>::log();
		}
	};

	void test()
	{
		int c[2][8][9];

		Remove_all_extents<decltype(c)>::log();
		cout << typeid(Remove_all_extents<decltype(c)>::type).name() << endl;
		// int
		/*
			调用了特化版本 Remove_all_extents<T[2]>
			调用了特化版本 Remove_all_extents<T[8]>
			调用了特化版本 Remove_all_extents<T[9]>
			调用了泛化 Remove_all_extents<T>
			int
		*/
	}

【可以发现没有调用T[]版本】

分析 std::remove_all_extents<decltype©>::type 的过程:

第一步:c的类型是 int[2][8][9],std::remove_all_extents<decltype©>::type(递归的类模板理解成函数调用)等价于

std::remove_all_extents<decltype(int[2][8][9])>::type,这个写法会调用编译器实例化出

remove_all_extents<int[2][8][9]>类;

第二步,remove_all_extents<int[2][8][9]>::type 满足 struct remove_all_extents<T[N]>特化版本,这个时候特别值得注意的是:满足该特化版本时,非类型模板参数N的值是2(而不是9,这里不要搞反),所以,remove_all_extents<int[2][8][9]>::type实际得到的类型是remove_all_extents<int[8][9]>::type;这里可以看到,通过remove_all_extents就把第一维的数字2给拿掉了,紧接着就开始进入递归了。

第三步,第二次满足的又是remove_all_extents的第一个特化版本,因此拿掉的是第二维的数字8,remove_all_extents<int[8][9]>::type;得到的类型变成了remove_all_extents<int[9]>::type,第二维数字又被拿掉了。

第四步,继续递归,第三次满足的又是remove_all_extents的第一个特化版本,因此拿掉的是第三维的数字9,所以,remove_all_extents<int[9]>::type;得到的类型变成了remove_all_extents::type,第三维数字又被拿掉了。

第五步,继续递归,第四次满足的确是remove_all_extents的泛化版本,这也预示着递归结束了,因为泛化版本的代码是using type = T; T这里是int类型,因此remove_all_extents::type最终就成了类型int。

std::remove_all_extents<decltype(int[2][8][9])>::type 整体过程:

一共实例化出来下面4个类:

cpp 复制代码
//a)remove_all_extents<int[2][8][9]>;
//b)remove_all_extents<int[8][9]>;
//c)remove_all_extents<int[9]>;
//d)remove_all_extents<int>;

4.8 std::integer_sequence(C++14)

std::integer_sequence C++14中引入的一个类模板,用于表示整数序列,包含在头文件#include

引入目的:在C++11引入了可变参模板后,缺少在编译期处理整数序列的方法,用于在编译期间处理整数。

4.8.1 std::integer_sequence的基本使用

cpp 复制代码
    // 使用折叠表达式输出序列(C++17)
    template <typename T, T... Vals>
    void print_sequence_cpp17(std::integer_sequence<T, Vals...>)
    {
        std::cout << "序列值(C++17): ";
        ((std::cout << Vals << " "), ...);  // C++17 折叠表达式
        // 展开后:((std::cout << 1 << " "), (std::cout << 2 << " "), (std::cout << 3 << " "))
        // 序列值(C++17): 1 2 3
        std::cout << std::endl;
    }

    void test()
    {
		std::integer_sequence<int, 1, 2, 3> seq1; // 定义一个整数序列,包含1, 2, 3
        /*
            分析:
            类型参数1:_Ty,可以取 int ,unsigned in ,char ,short 
            类型参数2:_Vals,非类型模板参数,一堆数字,与_Ty类型相同
        */
        print_sequence_cpp17(seq1);
    }

4.8.2 源码分析

源码如下:

cpp 复制代码
    template <class _Ty, _Ty... _Vals>
    struct integer_sequence 
    { 
        // sequence of integer parameters
        using value_type = _Ty;

        static constexpr size_t size() noexcept 
        {
            return sizeof...(_Vals);   // 固定写法,求参数数量
        }
    };

分析:

类型参数1:_Ty,可以取 int ,unsigned in ,char ,short

类型参数2:_Vals,非类型模板参数,一堆数字,与_Ty类型相同

4.8.3 std::make_integer_sequence (C++14)作用

std::make_integer_sequence 在C++14中引入的类模板别名,功能:自动生成0到N-1的连续整数序列。

源码如下:

cpp 复制代码
_EXPORT_STD template <class _Ty, _Ty _Size>
using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;

参数1:_Ty,指定整数类型,int,size_t等

参数2:指定序列长度

使用方法:

cpp 复制代码
	template<typename T,T...Args>
	void print_sequence(std::integer_sequence<T, Args...>)
	{
		((std::cout << Args << " "), ...);  // C++17 折叠表达式/
		cout << endl;
	}

	void test()
	{
		std::integer_sequence<int, 1, 2, 3> sq1;
		print_sequence(sq1);

		std::make_integer_sequence<int, 5> seq1;
		cout << "tmpobj的类型为:" << typeid(decltype(seq1)).name() << endl;
		// tmpobj的类型为:struct std::integer_sequence<int,0,1,2,3,4>
		print_sequence(seq1);
		// 0 1 2 3 4
	}

4.8.4 std::make_index_sequence (C++14)

在C++14中引入的模板别名,是 make_integer_sequence 的特化,专门用于生成 size_t 类型的整数序列。

源码如下:

cpp 复制代码
    _EXPORT_STD template <size_t _Size>
        using make_index_sequence = make_integer_sequence<size_t, _Size>;

测试:

cpp 复制代码
    void test()
    {
		std::make_index_sequence<4> obj2;
		print_sequence(obj2);
		// 0 1 2 3    
	}

4.8.5 实现 make_integer_sequence<int,5>

研究 如何通过类型 make_integer_sequence<int,5>得到类型:std::integer_sequence<int,0,1,2,3,4>,现在,实现一个 make_integer_sequence<int,5>的功能。

实现自定义的make_integer_sequence<int,5>
cpp 复制代码
  //向integer_sequence末尾插入元素:
  //泛化版本
  template<typename INTSEQ, unsigned int NewElem> //INTSEQ代表整个的std::integer_sequence< ......>类型
  struct IntSeq_PushBack; //因为不使用泛化版本,所以泛化版本可以只声明不定义。

  //向std::integer_sequence末尾插入元素:特化版本
  template<typename T,unsigned int...Elems,unsigned int NewElem>
  struct IntSeq_PushBack< std::integer_sequence<T, Elems...>, NewElem >
  {
      using type = std::integer_sequence<T, Elems..., NewElem>;
  };

  // 泛化版本
  template<typename T,unsigned int N>
  struct Integer_Sequence//实现std::make_integer_sequence功能
  {
      //依次遍历出4,3,2,1,应该往末尾插入元素,所以还应该引入IntSeq_PushBack操作。
      using type = typename IntSeq_PushBack<typename Integer_Sequence<T, N - 1>::type, N - 1>::type;
      // 这里理解成函数调用,把type理解成所调用的函数名
  };
  // 特化版本
  template <typename T>
  struct Integer_Sequence<T, 1> //这里是1,意味着递归到1就可以了
  {
      using type = std::integer_sequence<T, 0>; //这是0,递归到最后一个输出0
  };

  // 别名模板
  template<typename T,unsigned int N>
  using Integer_Sequence_T = typename Integer_Sequence<T, N>::type;

  void test()
  {
      Integer_Sequence_T<int, 4> tmpobj3;
      cout << "tmpobj3的类型为:" << typeid(decltype(tmpobj3)).name() << endl;
      // tmpobj的类型为:struct std::integer_sequence<int,0,1,2,3,4>
  }
分析实现原理
cpp 复制代码
(i) Integer_Sequence_T<int, 4> tmpobj3; 将别名转为:
(i) Integer_Sequence<int, 4>::type tmpobj3;  把type看成函数调用,开始调用 Integer_Sequence泛化版本
(i) 使用 Integer_Sequence ,T=int,N-1 = 4-1 =3 ,得到如下:
(i) using type = typename IntSeq_PushBack<typename Integer_Sequence<int,3>::type,3>::type;
     // 展开后:IntSeq_PushBack<int,3>::type 如下:
 (i) using type = typename IntSeq_PushBack<  typename Integer_Sequence< int,2 >::type,2 >::type;
     //展开  Integer_Sequence< int,2 >::type 如下:
 (i)using type = typename IntSeq_PushBack<  typename Integer_Sequence< int,1 >::type,1 >::type;
   //展开  Integer_Sequence< int,1 >::type ,开始调用特化版本,递归回退:
(i)using type = std::integer_sequence<int, 0>;  //递归结束,开始往回返
 (i)递归往回返的时候注意IntSeq_PushBack是向std::integer_sequence末尾插入内容
                    using type = std::integer_sequence<int, 0,1>
                using type = std::integer_sequence<int, 0,1,2>
            using type = std::integer_sequence<int, 0,1,2,3>
(i)最终结果
            using type = std::integer_sequence<int, 0,1,2,3>

4.8.6 实现逆向Integer_Sequence

生成一段逆向排列的元素 Integer_Sequence_Reverse

cpp 复制代码
   // 向 integer_sequence开头插入元素
    // 泛化版本
    template<typename INTSEQUENCE,unsigned int NewElem>//INTSEQUENCE代表整个的std::integer_sequence< ......>类型
    struct IntSeq_PushFront;//因为不使用泛化版本,所以泛化版本可以只声明不定义。
    // 特化版本
    // 向std::integer_sequence开头插入元素:特化版本
    template<typename T,unsigned int...Elems,unsigned int NewElem>
    struct IntSeq_PushFront< std::integer_sequence<T, Elems... >, NewElem  >
    {
        using type = std::integer_sequence<T, NewElem, Elems...>;
    };
    
    // 泛化版本
    template<typename T, unsigned int N,unsigned Count = 1>
    struct Integer_Sequence_Reverse
    {
        using type = typename IntSeq_PushFront<  typename Integer_Sequence_Reverse< T, N - 1 >::type, N - Count >::type;
    };

    //特化版本
    template <typename T, unsigned int N>
    struct Integer_Sequence_Reverse<T, N, N>
    {
        using type = std::integer_sequence<T, N - 1>;
    };
    //---------
    //定义别名模板
    template <typename T, unsigned int N>
using Integer_Sequence_Reverse_T = typename Integer_Sequence_Reverse<T, N>::type;
    void test()
    {
        // 问题1:逆向排列数字,生成一个类型Integer_Sequence_Reverse
        Integer_Sequence_Reverse_T<int, 4> tmpobj;
        cout << "tmpobj类型为:" << typeid(decltype(tmpobj)).name() << endl;
        // tmpobj类型为:struct std::integer_sequence<int,3,2,1,0>
}
        // 推导过程:
    //(i)Integer_Sequence_Reverse_T<int, 4> tmpobj4;   //等价于
    //(i)Integer_Sequence_Reverse<int, 4>::type tmpobj4; //把type看成函数调用
    //(i)    using type = IntSeq_PushFront<Integer_Sequence_Reverse< int,3>::type, 3 >::type;
    //(i)        //展开上行后面这个Integer_Sequence_Reverse< int,3>::type如下
    //(i)        using type = IntSeq_PushFront<Integer_Sequence_Reverse< int,2>::type, 2 >::type;
    //(i)            //展开上行后面这个Integer_Sequence_Reverse< int,2>::type如下
    //(i)            using type = IntSeq_PushFront<Integer_Sequence_Reverse< int,1>::type, 1 >::type;
    //(i)                //展开上行后面这个Integer_Sequence_Reverse< int,1>::type,调用的是特化版本
    //(i)                using type = std::integer_sequence<int, 0>; //递归结束往回返
    //(i)            using type = std::integer_sequence<int, 1,0>
    //(i)        using type = std::integer_sequence<int, 2,1,0>
    //(i)    using type = std::integer_sequence<int,3,2,1,0>
    //(i)//最终结果
    //(i)std::integer_sequence<int,3,2,1,0>

4.8.7 将一个数字重复多次生成一个类型Repeat_Integer

前面是通过递归调用的方式实现。现在这里通过递归继承的方式实现。

cpp 复制代码
   //泛化版本
    template <std::size_t Num, std::size_t RepeatTime, typename INTSEQ = std::integer_sequence<std::size_t> >  // INTSEQ代表整个的std::integer_sequence< ......>类型
    class Repeat_Integer;

    //特化版本1:
    template <std::size_t Num, std::size_t RepeatTime, std::size_t... index >
    class Repeat_Integer<Num, RepeatTime, std::integer_sequence<std::size_t, index...> > :
        public Repeat_Integer<Num, RepeatTime - 1, std::integer_sequence<std::size_t, index..., Num>>
    {
    };

    //特化版本2(用于结束递归)
    template <std::size_t Num, std::size_t... index >
    class Repeat_Integer<Num, 0, std::integer_sequence<std::size_t, index...> >
    {
    public:
        using type = std::integer_sequence<std::size_t, index...>;
    };

    //定义别名模板
    template <std::size_t Num, std::size_t RepeatTime>
    using Repeat_Integer_T = typename Repeat_Integer<Num, RepeatTime>::type;

    void test()
    {
        Repeat_Integer<1, 4>::type tmpobj;
        cout << "tmpobj5的类型为:" << typeid(decltype(tmpobj)).name() << endl;
        // tmpobj5的类型为:struct std::integer_sequence<unsigned __int64,1,1,1,1>
}
原理如下:
/*(1)因为Repeat_Integer<1, 4>::type tmpobj5; 代码行的存在,导致系统会根据Repeat_Integer特化版本1实例化出如下类:
Repeat_Integer<1, 4, std::integer_sequence<std::size_t>>
_nmsp3::Repeat_Integer<1, 4>::type tmpobj5;
cout << "tmpobj5的类型为:" << typeid(decltype(tmpobj5)).name() << endl; 
//希望显示的结果:struct std::integer_sequence<unsigned int,1,1,1,1>
	_nmsp3::Repeat_Integer<1, 4, std::integer_sequence<std::size_t,12,18,19> >::type tmpobj6;
cout << "tmpobj6的类型为:" << typeid(decltype(tmpobj6)).name() << endl;

(2)接着说tmpobj5,要想实例化出:Repeat_Integer<1, 4, std::integer_sequence<std::size_t>>
根据继承关系,编译器必须要实例化出他的父类,其实也就是:
Repeat_Integer<Num, 3, std::integer_sequence<std::size_t, 1>>
上面这个写法注意:std::integer_sequence模板参数的末尾,已经多出个1了,而且此时的index代表的不在是0个参数,而是1个参数了。
......
(3)在递归结束时,即 使用Repeat_Integer的特化版本2来实例化Repeat_Integer时,使用了using来定义类型别名type,
	  此时type就相当于类型std::integer_sequence<std::size_t, 1, 1, 1, 1>,因为是一种类继承关系,所以父类中定义的type在
	 子类中即可使用,因此Repeat_Integer<1, 4>::type这种写法毫无问题。*/

/*

4.9 std::is_union、is_class 、std::integral_constant

4.9.1 std::is_union 类模板(C++11)

C++11引入,用于判断某个类型是否时一个联合类型,这个过程在编译期间完成的。

cpp 复制代码
	struct A {};
	union B
	{
		int num;
		char type;
	};

	void test()
	{
		cout << std::is_union<A>::value << endl;  // 0
		cout << std::is_union<B>::value << endl; // 1

		// C++17 
		cout << std::is_union_v<A> << endl;  // 0
		cout << std::is_union_v<B> << endl; // 1
	}

4.9.2 std::is_class 类模板

std::is_class 类模板,判断某个类型是否是一个类类型, 在编译期间完成的。

cpp 复制代码
	struct A {};
	union B
	{
		int num;
		char type;
	};

	void test()
	{
		// 8.2 
		cout << std::is_class<A>::value << endl; // 1
		cout << std::is_class<B>::value << endl; // 0
		// C++17 
		cout << std::is_class_v<A> << endl; // 1
		cout << std::is_class_v<B> << endl;// 0
	}

4.9.3 integral_constant

integral : 整的,积分的 完整的

integral_constant是一个用于包装目的的类,将一个内置的值和类型包装起来。

cpp 复制代码
	struct A {};
	union B
	{
		int num;
		char type;
	};

	template <class T, T _Val>
	struct integral_constant
	{
		static constexpr T value = _Val;
		using value_type = T;
		using type = integral_constant;
		constexpr operator value_type() const noexcept
		{
			return value;
		}
		constexpr value_type operator()() const noexcept
		{
			return value;
		}
	};	
	void test()
	{
	    cout << std::integral_constant<int, 15>::value << endl;
		cout << std::integral_constant<bool, true>::value << endl;
		std::integral_constant<bool, !std::is_union<A>::value> myobj2;
		cout << myobj2.value << endl; // 1

		// 通过 integral_constant这样的包装,把 std::is_union<A>::value 包装为一个类型
		// 那么在很多需要用到类型的场合(比如函数返回类型中)就可以使用这个类型。
		// 不要忘记,!std::is_union<_nmsp1::B>::value值在编译时就能确定。
	}
相关推荐
im_AMBER39 分钟前
算法笔记 16 二分搜索算法
c++·笔记·学习·算法
6***x5451 小时前
C++在计算机视觉中的图像处理
c++·图像处理·计算机视觉·游戏引擎·logback·milvus
fpcc1 小时前
跟我学C++中级篇——内存访问违例
c++
E***q5392 小时前
C++内存对齐优化
开发语言·c++
D_evil__2 小时前
[C++高频精进] 文件IO:文件操作
c++
别动哪条鱼3 小时前
MP4转AAC转换器C++
c++·ffmpeg·音视频·aac
2301_807997383 小时前
代码随想录-day47
数据结构·c++·算法·leetcode
李日灐3 小时前
手搓简单 string 库:了解C++ 字符串底层
开发语言·c++
Elias不吃糖3 小时前
LeetCode每日一练(3)
c++·算法·leetcode