本文继续介绍第四章标准库的典型内容之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值在编译时就能确定。
}