类模板
初始类模板
类模板不是类,只有实例化类模板,编译器才能生成实际的类。
类似于函数模板必须实例化才有对应的函数
定义类模板
和普通类的区别只是多了一个 template<typename T>
和函数模板一样,其实类模板的语法也就是:
template< 形参列表 > 类声明
函数模板中形参列表能写的东西,类模板都可以
使用类模板
[!tip]
必须显式的指明类模板的类型实参,并且没有办法推导
// Test<void> t; // Error!无法声明void变量 Test<int> t2; // Test t3; // Error!没有显示指明类型,同时无法推导 Test t4{ 1 }; // C++17 OK!
Test t4{ 1 };
C++17 增加了类模板实参推导,也就是说类模板也可以像函数模板一样被推导,而不需要显式的写明模板类型参数了,这里的Test
被推导为Test<int>
。
类模板推导
对于简单的类模板,通常可以普通的类似函数模板一样的自动推导
template<class T>
struct A{
A(T, T);
};
auto y = new A{1, 2}; // 分配的类型是 A<int>
new 表达式中一样可以
[!important]
用户定义的推导指引
模板名称(类型a)->模板名称<想要让类型a被推导为的类型>
如果涉及的是一类类型,那么就需要加上
template
,然后使用它的模板形参。稍微有点难度的需求:
template<class Ty, std::size_t size> struct array { Ty arr[size]; }; ::array arr{1, 2, 3, 4, 5}; // Error!
类模板 array 同时使用了类型模板形参与非类型模板形参,保有了一个成员是数组。
它无法被我们直接推导出类型,此时就需要定义推导指引**。
template<typename T, typename ...Args> array(T t,Args...) -> array<T, sizeof...(Args) + 1>;
让模板形参单独写一个 T 占位,放到形参列表中,并且写一个模板类型形参包用来处理任意个参数
直接使用 sizeof... 获取形参包的元素个数,然后再 +1 ,因为先前用了一个模板形参占位
有默认实参的模板形参
类模板一样可以有默认实参。
template<typename T = int>
struct X{};
X x; // x 是 X<int> C++17 起 OK
X<> x2; // x2 是 X<int>C++14起可以省略
必须达到 C++17 有 CTAD
,才可以在全局、函数作用域声明为 X
这种形式,才能省略 <>
。
在类中声明一个,有默认实参的类模板类型的数据成员(静态或非静态,是否类内定义都无所谓),不管是否达到 C++17,都不能省略 <>
。
[!warning]
gcc13.2 有不同行为,开启
std=c++17
,类内定义的静态数据成员省略<>
可以通过编译。但是,总而言之,不要类内声明中省略<>
。
- 给常量模板形参以默认值:
template<class T, std::size_t N = 10>
模板模板形参
类模板的模板类型形参可以接受一个类模板作为参数,我们将它称为:模板模板形参。
template<typename T> struct X {}; template<template<typename T> typename C> struct Test {}; Test<X>arr;
template<typename T> typename C
我们分两部分看就好
- 前面的
template<typename T>
就是我们要接受的类模板它的模板列表,是需要一模一样的,比如类模板 X 就是。- 后面的
typename
是语法要求,需要声明这个模板模板形参的名字,可以自定义,这样就引入了一个模板模板形参。
详细的语法形式:
template < 形参列表 > typename(C++17)|class 名字(可选) (1) template < 形参列表 > typename(C++17)|class 名字(可选) = default (2) template < 形参列表 > typename(C++17)|class ... 名字(可选) (3) (C++11 起)
[!tip]
可以有名字的模板模板形参包。
其实就是形参包的一种,能接受任意个数的类模板
template<typename T> struct X{}; template<typename T> struct X2 {}; template<template<typename T>typename...Ts> struct Test{}; Test<X, X2, X, X>t; // 我们可以传递任意个数的模板实参
普通的有形参包的类模板也都是同理:
template<typename... T> struct my_array{ int arr[sizeof...(T)]; // 保有的数组大小根据模板类型形参的元素个数 }; template<typename Ty, template<typename... T> typename C = my_array > struct Array { C<Ty>array; }; Array<int>arr;
成员函数模板
成员函数模板基本上和普通函数模板没多大区别,唯一需要注意的是,它大致有两类:
- 类模板中的成员函数模板
- 普通类中的成员函数模板
[!tip]
对于模板类里的成员函数不是函数模板,在类模板实例化为具体类型的时候,成员函数也被实例化为具体。
可变参数类模板
形参包与包展开等知识,在类模板中是通用的。
template<typename ...Args>
struct X {
X(Args...args) :value{ args... } {} // 参数展开
std::tuple<Args...>value; // 类型形参包展开
};
X x{ 1,"2",'3',4. }; // x 的类型是 X<int,const char*,char,double>
std::cout << std::get<1>(x.value) << '\n'; // 2
[!warning]
需要注意的是字符串字面量的类型是
const char[N]
,之所以被推导为const char*
在于数组之间不能“拷贝”。它隐式转换为了指向数组首地址的指针,类型自然也被推导为const char*
。
类模板分文件
类模板也没有办法分文件。
通常就是统一写到 .h
文件中,或者大家约定俗成了一个 .hpp
后缀,这个通常用来放模板。