类模板

初始类模板

类模板不是类,只有实例化类模板,编译器才能生成实际的类。

类似于函数模板必须实例化才有对应的函数

定义类模板

和普通类的区别只是多了一个 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++17CTAD,才可以在全局、函数作用域声明为 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]

  1. 可以有名字的模板模板形参包

    其实就是形参包的一种,能接受任意个数的类模板

    template<typename T>
    struct X{};
    
    template<typename T>
    struct X2 {};
    
    template<template<typename T>typename...Ts>
    struct Test{};
    
    Test<X, X2, X, X>t;     // 我们可以传递任意个数的模板实参
    
  2. 普通的有形参包的类模板也都是同理:

    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 后缀,这个通常用来放模板。

©OZY all right reserved该文件修订时间: 2025-09-20 05:42:10

评论区 - 02_类模板

results matching ""

    No results matching ""