C++ Primer (第五版)-第十六章模板与泛型编程

发布于:2025-07-03 ⋅ 阅读:(29) ⋅ 点赞:(0)

文章目录

定义模板

函数模板

template <typename T>
int compare (const T &V1, const T &v2)
{
if(v1<v2)  return -1;
if(v2<v1) return 1;
return 0;

}

模板定义以关键字tempalte 开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用小于号和大于号包围起来
在模板定义中,模板参数列表不能为空。

实例化函数模板

在这里插入图片描述

模板类型参数

上述的compare 函数有一个模板类型参数。一般将类型参数看作类型说明符就像内置类型或类类型说明符一样。类型参数可与i用来指定返回类型或者函数的参数类型,以及函数体内用于标量声明或者类型转换。

template<typename T> T FOO(t* p)
{
  T tmp=*p// tmp 的类型是指针p指向的类型
  return tmp;
  }

类型参数前必须使用关键字class或typename
在这里插入图片描述
在这里插入图片描述

非类型模板参数

在这里插入图片描述
在这里插入图片描述
在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数。
非类型模板参数的模板实参必须是常量表达式。

inline 和constexpr的函数模板

inline或constexpr说明符放在模板参数列表之后,返回类型之前:

template<tempename T>inline T min(const T&,const T&);//正确inline说明符跟在模板参数列表之后

编写类型无关的代码

  • 模板中的函数参数是const的引用
  • 函数体中条件判断仅使用<比较运算
  • 在这里插入图片描述
    如果我们真的关心类型无关和可移植性,可能需要使用less来定义函数
template<typename T> int compare (const T &V1,const T &v2)
{
  if(less<T>() (V1,V2))  return -1;
  if(less<t>()(V2,V1)) return 1;
  return 0;

}

模板程序应该尽量减少对实参类型的要求。

模板编译

当编译器遇到一个模板定义时,它并不生成代码,只有当我们实例化出模板的一个特定版本时,编译器才会生成艾玛。当我们使用而非定义模板时,编译器才生成代码。这一特性影响我们如何阻止代码以及错误和时被检测到。
通常,我们调用一个函数 时,编译器只需要掌握函数声明,类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,哦我们将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
模板则不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数定义。因此,与非模板代码不同,模板的头文件通常既包含声明也包含定义。
函数模板和类模板成员函数的定义通常放在头文件中。
在这里插入图片描述

例子

以下是C++ 中模板和头文件相关示例:

1. 模板定义(假设在 template_example.h 头文件中)

// 函数模板
template <typename T>
T add(T a, T b) {
    return a + b;
}

// 类模板
template <typename T>
class MyClass {
public:
    T value;
    MyClass(T v) : value(v) {}
    T getValue() {
        return value;
    }
};

这里定义了一个函数模板 add 用于对相同类型的两个值相加,还定义了一个类模板 MyClass ,它有一个成员变量 value 以及相关的成员函数 getValue

2. 模板使用(假设在 main.cpp 源文件中)

#include "template_example.h"
#include <iostream>

int main() {
    // 使用函数模板
    int result1 = add(3, 5);
    std::cout << "Addition result (int): " << result1 << std::endl;

    double result2 = add(3.5, 5.5);
    std::cout << "Addition result (double): " << result2 << std::endl;

    // 使用类模板
    MyClass<int> myObj(10);
    std::cout << "MyClass value: " << myObj.getValue() << std::endl;

    return 0;
}

main.cpp 中,通过 #include "template_example.h" 包含了定义模板的头文件,这样就满足了模板使用时相关定义可见的要求。在 main 函数里,分别对函数模板 add 进行了 intdouble 类型的实例化调用,还实例化了类模板 MyClassMyClass<int> 并使用其成员函数。

在这个过程中,模板设计者(编写 template_example.h 的人)提供了模板定义;模板用户(编写 main.cpp 的人)包含了模板定义所在的头文件来满足模板实例化时名字可见的要求 。

大多数编译错误在实例化期间报告

=
在这里插入图片描述

类模板

编译器不能为类模板推断模板参数类型,为了使用类模板我们必须在模板名后的尖括号中提供额外信息-来代替模板参数的模板实参列表。

定义类模板

#include <memory>
#include <vector>
#include <initializer_list>
#include <stdexcept>
#include <string>

template <typename T>
class Blob {
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // 构造函数
    Blob();
    Blob(std::initializer_list<T> il);
    // Blob中的元素数目
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // 添加和删除元素
    void push_back(const T &t) { data->push_back(t); }
    // 移动版本,参见13.6.3节(第484页)
    void push_back(T &&t) { data->push_back(std::move(t)); }
    void pop_back();
    // 元素访问
    T& back();
    T& operator[](size_type i); // 在14.5节(第501页)中定义

private:
    std::shared_ptr<std::vector<T>> data;
    // 若data[i]无效,则抛出msg
    void check(size_type i, const std::string &msg) const;
};

// 构造函数实现
template <typename T>
Blob<T>::Blob() : data(std::make_shared<std::vector<T>>()) {}

template <typename T>
Blob<T>::Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) {}

// pop_back函数实现
template <typename T>
void Blob<T>::pop_back() {
    check(0, "pop_back on empty Blob");
    data->pop_back();
}

// back函数实现
template <typename T>
T& Blob<T>::back() {
    check(0, "back on empty Blob");
    return data->back();
}

// operator[]函数实现
template <typename T>
T& Blob<T>::operator[](size_type i) {
    check(i, "subscript out of range");
    return (*data)[i];
}

// check函数实现
template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const {
    if (i >= data->size()) {
        throw std::out_of_range(msg);
    }
}


int main() {
    Blob<int> intBlob = {1, 2, 3};
    std::cout << "Blob size: " << intBlob.size() << std::endl;
    intBlob.push_back(4);
    std::cout << "After push_back, Blob back element: " << intBlob.back() << std::endl;
    intBlob.pop_back();
    std::cout << "After pop_back, Blob size: " << intBlob.size() << std::endl;
    try {
        intBlob[10]; // 故意触发越界异常
    } catch (const std::out_of_range& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

在这里插入图片描述

在模板作用域中引用模板类型

在这里插入图片描述

类模板成员函数

在这里插入图片描述
在这里插入图片描述

在类代码内简化模板类名的使用

在这里插入图片描述
在这里插入图片描述

在类模板外使用类模板名

在这里插入图片描述

类模板和友元

#include <iostream>

// 模板友元类
template <typename U>
class TemplateFriend {
public:
    void accessBlob(Blob<U>& b);
};

// 类模板
template <typename T>
class Blob {
    template <typename U>
    friend class TemplateFriend; // 模板友元声明,授权给所有友元模板实例
private:
    T data;
public:
    Blob(T t) : data(t) {}
    T getData() const { return data; }
};

// 模板友元类中访问Blob类模板实例的成员函数定义
template <typename U>
void TemplateFriend<U>::accessBlob(Blob<U>& b) {
    std::cout << "Accessed data in Blob<" << typeid(U).name() << ">: " << b.getData() << std::endl;
}

在这里插入图片描述

#include <iostream>
#include <vector>
#include <memory>
#include <stdexcept>

// 前置声明
template <typename> class BlobPtr;
template <typename> class Blob;

// 前置声明运算符重载
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> bool operator!=(const Blob<T>&, const Blob<T>&);

// Blob类模板定义
template <typename T>
class Blob {
    friend class BlobPtr<T>;
    friend bool operator==<T>(const Blob<T>&, const Blob<T>&);

public:
    using value_type = T;
    using size_type = typename std::vector<T>::size_type;

    // 构造函数
    Blob() : data(std::make_shared<std::vector<T>>()) {}
    Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) {}

    // 容量操作
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    // 修改操作
    void push_back(const T& t) { data->push_back(t); }
    void push_back(T&& t) { data->push_back(std::move(t)); }
    
    void pop_back() {
        check(0, "pop_back on empty Blob");
        data->pop_back();
    }

    // 元素访问
    T& front() {
        check(0, "front on empty Blob");
        return data->front();
    }
    
    const T& front() const {
        check(0, "front on empty Blob");
        return data->front();
    }

    T& back() {
        check(0, "back on empty Blob");
        return data->back();
    }
    
    const T& back() const {
        check(0, "back on empty Blob");
        return data->back();
    }

    T& operator[](size_type i) {
        check(i, "subscript out of range");
        return (*data)[i];
    }
    
    const T& operator[](size_type i) const {
        check(i, "subscript out of range");
        return (*data)[i];
    }

private:
    std::shared_ptr<std::vector<T>> data;

    void check(size_type i, const std::string& msg) const {
        if (i >= data->size())
            throw std::out_of_range(msg);
    }
};

// BlobPtr类模板定义 - 修正自增/自减逻辑
template <typename T>
class BlobPtr {
public:
    BlobPtr() : curr(0) {}
    BlobPtr(Blob<T>& a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    // 解引用运算符
    T& operator*() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }

    T* operator->() const {
        return &this->operator*();
    }

    // 前置自增运算符 - 修正:先检查后增加(确保新位置有效)
    BlobPtr& operator++() {
        check(curr, "increment past end of BlobPtr");  // 检查当前位置是否合法
        ++curr;  // 移动到下一个位置
        if (curr != wptr.lock()->size()) {
            check(curr, "increment past end of BlobPtr");  // 确保新位置有效
        }
        return *this;
    }

    // 后置自增运算符
    BlobPtr operator++(int) {
        BlobPtr ret = *this;
        ++*this;  // 复用前置++
        return ret;
    }

    // 前置自减运算符 - 修正:先检查后减少(确保新位置有效)
    BlobPtr& operator--() {
        if (curr == 0) {
            throw std::out_of_range("decrement past begin of BlobPtr");
        }
        --curr;  // 移动到前一个位置
        check(curr, "decrement past begin of BlobPtr");  // 确保新位置有效
        return *this;
    }

    // 后置自减运算符
    BlobPtr operator--(int) {
        BlobPtr ret = *this;
        --*this;  // 复用前置--
        return ret;
    }

private:
    std::weak_ptr<std::vector<T>> wptr;
    size_t curr;

    std::shared_ptr<std::vector<T>> check(size_t i, const std::string& msg) const {
        auto ret = wptr.lock();
        if (!ret)
            throw std::runtime_error("unbound BlobPtr");
        if (i >= ret->size())
            throw std::out_of_range(msg);
        return ret;
    }
};

// 相等运算符 - 比较内容而非指针
template <typename T>
bool operator==(const Blob<T>& a, const Blob<T>& b) {
    if (a.data == b.data) return true;  // 同一对象
    if (a.size() != b.size()) return false;  // 大小不同
    
    // 逐元素比较
    for (size_t i = 0; i < a.size(); ++i) {
        if (a[i] != b[i]) return false;
    }
    return true;
}

// 不等运算符
template <typename T>
bool operator!=(const Blob<T>& a, const Blob<T>& b) {
    return !(a == b);
}

// 测试函数
int main() {
    try {
        // 创建Blob并添加元素
        Blob<int> blob = {1, 2, 3, 4};
        
        // 使用BlobPtr遍历元素
        BlobPtr<int> ptr(blob);
        std::cout << "遍历元素: ";
        for (size_t i = 0; i < blob.size(); ++i) {
            std::cout << *ptr << " ";
            ++ptr;  // 测试前置自增
        }
        std::cout << std::endl;
        
        // 测试后置自增
        BlobPtr<int> ptr2(blob);
        std::cout << "后置自增测试: " << *ptr2++ << " " << *ptr2 << std::endl;
        
        // 测试自减
        --ptr;  // 回到最后一个元素
        std::cout << "自减测试: " << *ptr << std::endl;
        
        // 测试相等运算符
        Blob<int> blob2 = {1, 2, 3, 4};
        Blob<int> blob3 = {1, 2, 3};
        std::cout << "相等测试: " << (blob == blob2) << " " << (blob == blob3) << std::endl;
        
        // 测试异常处理
        BlobPtr<int> emptyPtr;
        // *emptyPtr;  // 会抛出异常:unbound BlobPtr
        
    } catch (const std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
    
    return 0;
}
/*
遍历元素: 1 2 3 4 
后置自增测试: 1 2
自减测试: 4
相等测试: 1 0
*/

通用和特定的模板友好关系

在这里插入图片描述

模板类型别名

在这里插入图片描述

类模板的static成员

// templateStatic.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <iostream>
#include <string>

// 模板类定义
template <typename T>
class MyTemplateClass {
public:
    // 静态成员函数声明
    static void staticFunction();

    // 静态数据成员声明
    static T staticData;

    // 普通成员函数,用于展示静态数据成员的使用
    void printStaticData() {
        std::cout << "Static data value: " << staticData << std::endl;
    }
};

// 模板类静态数据成员的定义,需要指定模板参数
template <typename T>
T MyTemplateClass<T>::staticData = T();

// 模板类静态成员函数的定义,需要指定模板参数
template <typename T>
void MyTemplateClass<T>::staticFunction() {
    std::cout << "This is a static function of MyTemplateClass with type " << typeid(T).name() << std::endl;
}

int main() {
    // 使用int类型实例化模板类
    MyTemplateClass<int> intInstance;
    MyTemplateClass<int>::staticFunction();  // 通过类名调用静态成员函数
    intInstance.printStaticData();  // 通过对象调用普通成员函数来打印静态数据成员

    MyTemplateClass<int>::staticData = 42;  // 修改int实例化的静态数据成员
    intInstance.printStaticData();  // 再次打印验证修改

    // 使用std::string类型实例化模板类
    MyTemplateClass<std::string> stringInstance;
    MyTemplateClass<std::string>::staticFunction();  // 通过类名调用静态成员函数
    stringInstance.printStaticData();  // 通过对象调用普通成员函数来打印静态数据成员

    MyTemplateClass<std::string>::staticData = "Hello, template!";  // 修改string实例化的静态数据成员
    stringInstance.printStaticData();  // 再次打印验证修改

    return 0;
}

在这里插入图片描述

模板参数

一个模板参数的名字也没有什么内在含义

模板参数与作用域

在这里插入图片描述

模板声明

在这里插入图片描述

// templateStatic.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <iostream>
#include <string>

// 模板类定义
template <typename T>
class MyTemplateClass {
public:
    // 静态成员函数声明
    static void staticFunction();

    // 静态数据成员声明
    static T staticData;

    // 普通成员函数,用于展示静态数据成员的使用
    void printStaticData() {
        std::cout << "Static data value: " << staticData << std::endl;
    }
};

// 模板类静态数据成员的定义,需要指定模板参数
template <typename T>
T MyTemplateClass<T>::staticData = T();

// 模板类静态成员函数的定义,需要指定模板参数
template <typename T>
void MyTemplateClass<T>::staticFunction() {
    std::cout << "This is a static function of MyTemplateClass with type " << typeid(T).name() << std::endl;
}

int main() {
    // 使用int类型实例化模板类
    MyTemplateClass<int> intInstance;
    MyTemplateClass<int>::staticFunction();  // 通过类名调用静态成员函数
    intInstance.printStaticData();  // 通过对象调用普通成员函数来打印静态数据成员

    MyTemplateClass<int>::staticData = 42;  // 修改int实例化的静态数据成员
    intInstance.printStaticData();  // 再次打印验证修改

    // 使用std::string类型实例化模板类
    MyTemplateClass<std::string> stringInstance;
    MyTemplateClass<std::string>::staticFunction();  // 通过类名调用静态成员函数
    stringInstance.printStaticData();  // 通过对象调用普通成员函数来打印静态数据成员

    MyTemplateClass<std::string>::staticData = "Hello, template!";  // 修改string实例化的静态数据成员
    stringInstance.printStaticData();  // 再次打印验证修改

    return 0;
}

默认模板实参

// Template_Compare.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <functional>
#include <string>

// compare有一个默认模板实参less<T>和一个默认函数实参F()
template <typename T, typename F = std::less<T>>
int compare(const T& v1, const T& v2, F f = F()) {
    if (f(v1, v2)) return -1;
    if (f(v2, v1)) return 1;
    return 0;
}

int main() {
    // 测试整数比较,使用默认比较准则(std::less<int>)
    int num1 = 5;
    int num2 = 10;
    int result1 = compare(num1, num2);
    if (result1 == -1) {
        std::cout << num1 << " 小于 " << num2 << std::endl;
    }
    else if (result1 == 1) {
        std::cout << num1 << " 大于 " << num2 << std::endl;
    }
    else {
        std::cout << num1 << " 等于 " << num2 << std::endl;
    }

    // 测试字符串比较,使用默认比较准则(std::less<std::string>)
    std::string str1 = "apple";
    std::string str2 = "banana";
    int result2 = compare(str1, str2);
    if (result2 == -1) {
        std::cout << str1 << " 小于 " << str2 << std::endl;
    }
    else if (result2 == 1) {
        std::cout << str1 << " 大于 " << str2 << std::endl;
    }
    else {
        std::cout << str1 << " 等于 " << str2 << std::endl;
    }

    // 自定义比较准则,用于比较整数绝对值大小
    auto abs_compare = [](int a, int b) {
        return std::abs(a) < std::abs(b);
        };
    int num3 = -8;
    int num4 = 6;
    int result3 = compare(num3, num4, abs_compare);
    if (result3 == -1) {
        std::cout << "| " << num3 << " | 小于 | " << num4 << " |" << std::endl;
    }
    else if (result3 == 1) {
        std::cout << "| " << num3 << " | 大于 | " << num4 << " |" << std::endl;
    }
    else {
        std::cout << "| " << num3 << " | 等于 | " << num4 << " |" << std::endl;
    }

    return 0;
}

模板默认参数与类模板

无论何时使用一个类模板。我们都必须在模板名之后街上尖括号,尖括号指出类必须从一个模板实例化而来。
在这里插入图片描述
在这里插入图片描述

#include <iostream>

// 模板类定义
template <class T = int> class Numbers {
public:
    // 构造函数
    Numbers(T v = 0) : val(v) {}

    // 获取存储的值
    T getValue() const {
        return val;
    }

    // 设置存储的值
    void setValue(T newVal) {
        val = newVal;
    }

private:
    T val;
};

int main() {
    // 显式指定类型实例化
    Numbers<long double> lots_of_precision(3.14159265358979323846);
    std::cout << "lots_of_precision的值: " << lots_of_precision.getValue() << std::endl;

    // 使用默认类型实例化
    Numbers<> average_precision;
    average_precision.setValue(10);
    std::cout << "average_precision的值: " << average_precision.getValue() << std::endl;

    return 0;
}

成员模板

一个类(无论是普通类还是类模板),可以包含本身是模板的成员函数。这种成员被称为成员模板。

#include <iostream>
#include <memory>

// 函数对象类,对给定指针执行delete
class DebugDelete {
public:
    DebugDelete(std::ostream &s = std::cerr) : os(s) {}
    // 与任何函数模板相同,T的类型由编译器推断
    template <typename T> void operator()(T *p) const {
        os << "deleting unique_ptr" << std::endl;
        delete p;
    }
private:
    std::ostream &os;
};

int main() {
    // 示例1:使用默认的std::cerr输出调试信息
    int* ptr1 = new int(42);
    DebugDelete debugDelete1;
    debugDelete1(ptr1);

    // 示例2:指定输出流为std::cout
    double* ptr2 = new double(3.14);
    DebugDelete debugDelete2(std::cout);
    debugDelete2(ptr2);

    // 示例3:与std::unique_ptr结合使用
    std::unique_ptr<int, DebugDelete> ptr3(new int(10), DebugDelete());
    // 当ptr3超出作用域时,DebugDelete的operator()会被调用
    // 会打印调试信息并释放内存

    return 0;
}

模板自己的类型参数成为友元

template class Bar:定义了一个类模板 Bar,Type 是模板参数,后续可代入 int、std::string 等具体类型实例化类。
friend Type;:这是核心逻辑。当用某一具体类型(比如 int)实例化 Bar 时(如 Bar barObj; ),Type 就被替换为 int,代码等价于 friend int; ,意味着 int 类型(及其对应对象、操作)能访问 Bar 类的私有 / 保护成员 。

template <typename Type> 
class Bar {
    friend Type; 
private:
    Type data; 
public:
    Bar(Type val) : data(val) {}
};

int main() {
    Bar<int> bar(10);
    // 因 friend int; ,int 类型相关操作(这里直接用 int 变量访问)可碰私有成员
    int x = bar.data; // 合法,不会编译报错
    return 0;
}

模板类型别名

模板类型别名(Template Type Alias)是 C++11 引入的一项特性,允许为模板类或模板函数定义简化的别名,从而提高代码可读性和可维护性。下面通过具体示例详细说明:

1. 基本语法:使用 using 定义模板别名

通过 template<typename... Args> using 别名 = 原模板<Args...> 的形式定义。

示例 1:为 std::pair 定义模板别名

#include <utility>

// 定义模板别名 twin,固定两个类型参数相同
template<typename T>
using twin = std::pair<T, T>;

// 使用别名创建对象
twin<int> coords(10, 20);        // 等价于 std::pair<int, int>
twin<std::string> names("Alice", "Bob");  // 等价于 std::pair<std::string, std::string>

示例 2:为函数模板定义别名

#include <vector>
#include <functional>

// 定义接受 vector<T> 并返回 T 的函数类型别名
template<typename T>
using VectorProcessor = std::function<T(const std::vector<T>&)>;

// 使用别名声明函数变量
VectorProcessor<int> sum = [](const std::vector<int>& v) {
    int total = 0;
    for (int x : v) total += x;
    return total;
};

2. 为什么需要模板别名?

  • 简化复杂模板:当模板参数较多或嵌套层级深时,别名能显著减少代码冗余。

    // 原始写法(复杂)
    std::unordered_map<std::string, std::vector<std::pair<int, double>>> data;
    
    // 使用别名(简洁)
    template<typename K, typename V>
    using MapVectorPair = std::unordered_map<K, std::vector<std::pair<int, V>>>;
    
    MapVectorPair<std::string, double> data;  // 清晰易读
    
  • 实现模板特化:通过别名隐藏底层实现,方便统一修改。

    // 根据平台选择不同的容器实现
    #ifdef _WIN32
    template<typename T>
    using PlatformVector = std::vector<T>;  // Windows 使用 std::vector
    #else
    template<typename T>
    using PlatformVector = std::deque<T>;   // Linux 使用 std::deque
    #endif
    
    // 用户代码无需关心底层实现
    PlatformVector<int> numbers;
    

3. typedef 的对比

  • typedef 无法直接用于模板,而 using 可以。

    // 错误:typedef 不能直接定义模板别名
    typedef std::vector<T> Vec;  // 无法编译
    
    // 正确:使用 using 定义模板别名
    template<typename T>
    using Vec = std::vector<T>;
    
  • using 语法更直观,尤其对于函数指针类型。

    // 使用 typedef 定义函数指针类型(复杂)
    typedef void (*Callback)(int, double);
    
    // 使用 using 定义(简洁)
    using Callback = void(*)(int, double);
    

4. 进阶应用:模板别名与模板参数

  • 固定部分模板参数:通过别名绑定部分参数,生成新模板。

    #include <memory>
    
    // 固定 allocator 为 std::allocator 的 vector 别名
    template<typename T>
    using MyVector = std::vector<T, std::allocator<T>>;
    
    // 使用别名创建 vector
    MyVector<int> vec;  // 等价于 std::vector<int, std::allocator<int>>
    
  • 结合模板元编程:在编译期进行类型计算。

    // 条件选择类型的别名模板
    template<bool Condition, typename T, typename U>
    using ConditionalType = typename std::conditional<Condition, T, U>::type;
    
    // 根据条件选择不同类型
    ConditionalType<(sizeof(int) > 4), long, int> num;  // 在 64 位系统上等价于 long
    

5. 注意事项

  1. 别名不是新类型:模板别名只是语法糖,不会创建新的类型。

    using IntVector = std::vector<int>;
    static_assert(std::is_same_v<IntVector, std::vector<int>>, "Same type");  // 通过编译
    
  2. 别名模板不能偏特化:但可以通过原模板的特化间接实现。

    template<typename T>
    using Container = std::vector<T>;
    
    // 错误:不能直接特化别名模板
    // template<> using Container<bool> = std::vector<bool>;
    
    // 正确:特化原模板
    template<>
    struct std::vector<bool> { /* 特化实现 */ };
    

总结

模板类型别名是 C++ 中强大的抽象工具,能够:

  • 简化复杂模板的使用,提高代码可读性。
  • 实现平台无关的代码抽象。
  • 结合模板元编程实现编译期类型计算。

合理使用模板别名可以让代码更加简洁、灵活,同时保持类型系统的清晰性。
在这里插入图片描述

类模板的static成员

在这里插入图片描述

模板默认实参与类模板

在这里插入图片描述

template <class T=int> 
class Numbers
{
public:
  Number< T v=0):val(v)
  {
}
private:
  T val;
  };
  Number<long double> losts_pre;
  Number<>  precision;

成员模板

一个类可以包含本身是模板的成员函数,这种成员被称为成员模板,成员模板不能是虚函数。

普通类的成员模板-删除器

#include <iostream>
#include <memory>

class DebugDelete {
public:
    DebugDelete(std::ostream &s = std::cerr) : os(s) {}
    template<typename T>
    void operator()(T *p) const {
        os << "delete unique_ptr" << std::endl;
        delete p;
    }
private:
    std::ostream &os;
};

class MyClass {
public:
    MyClass(int val) : value(val) {
        std::cout << "MyClass constructed with value: " << value << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destructed with value: " << value << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    // 使用 DebugDelete 作为 unique_ptr 的删除器
    {
        std::unique_ptr<MyClass, DebugDelete> ptr(
            new MyClass(42),
            DebugDelete(std::cout)
        );
        std::cout << "ptr value: " << ptr->getValue() << std::endl;
        // ptr 离开作用域时,DebugDelete 会被调用
    }

    // 使用默认的 cerr 流
    {
        std::unique_ptr<int, DebugDelete> intPtr(
            new int(100),
            DebugDelete()
        );
        std::cout << "intPtr value: " << *intPtr << std::endl;
    }

    // 用于原始指针的手动删除
    double *rawPtr = new double(3.14);
    DebugDelete()(rawPtr); // 直接调用删除器
   double *p=new double;
   DebugDelete p;
   d(p);
    return 0;
}    

类模板的成员模板

 #include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

// Blob类模板定义
template<typename T>
class Blob {
public:
    using value_type = T;
    using size_type = typename std::vector<T>::size_type;

    // 默认构造函数
    Blob() : data(std::make_shared<std::vector<T>>()) {}
    
    // 从初始化列表构造
    Blob(std::initializer_list<T> il) : 
        data(std::make_shared<std::vector<T>>(il)) {}
    
    // 从迭代器范围构造(关键实现)
    template <typename It>
    Blob(It b, It e) : 
        data(std::make_shared<std::vector<T>>(b, e)) {}

    // 元素访问
    T& front() { return data->front(); }
    const T& front() const { return data->front(); }
    
    T& back() { return data->back(); }
    const T& back() const { return data->back(); }
    
    T& operator[](size_type i) { return (*data)[i]; }
    const T& operator[](size_type i) const { return (*data)[i]; }

    // 容量操作
    bool empty() const { return data->empty(); }
    size_type size() const { return data->size(); }

private:
    std::shared_ptr<std::vector<T>> data;
};

// 辅助函数:简化Blob创建
template<typename T>
Blob<T> make_blob(std::initializer_list<T> il) {
    return Blob<T>(il);
}

int main() {
    // 1. 使用初始化列表构造
    Blob<int> numbers = {1, 2, 3, 4, 5};
    std::cout << "numbers size: " << numbers.size() << std::endl;
    std::cout << "numbers[2]: " << numbers[2] << std::endl;

    // 2. 使用迭代器范围构造(从vector)
    std::vector<std::string> words = {"hello", "world", "!"};
    Blob<std::string> text(words.begin(), words.end());
    std::cout << "text size: " << text.size() << std::endl;
    std::cout << "text.back(): " << text.back() << std::endl;

    // 3. 使用迭代器范围构造(从数组)
    double arr[] = {1.1, 2.2, 3.3};
    Blob<double> doubles(arr, arr + 3);
    std::cout << "doubles[1]: " << doubles[1] << std::endl;

    // 4. 使用辅助函数构造
    auto chars = make_blob({'a', 'b', 'c'});
    std::cout << "chars.front(): " << chars.front() << std::endl;

    return 0;
}    

控制实例化

对每个实例化声明,在程序中某个位置必须有其显式的实例化定义。
实例化定义会实例化所有成员
在这里插入图片描述

// ====================
// blob.h - Blob类模板定义
// ====================
#pragma once
#include <memory>
#include <vector>
#include <string>

template<typename T>
class Blob {
public:
    using value_type = T;
    using size_type = typename std::vector<T>::size_type;

    Blob();
    Blob(std::initializer_list<T> il);
    
    T& front();
    const T& front() const;
    
    T& back();
    const T& back() const;
    
    T& operator[](size_type i);
    const T& operator[](size_type i) const;

    bool empty() const;
    size_type size() const;

private:
    std::shared_ptr<std::vector<T>> data;
};

// 函数模板声明
template<typename T>
int compare(const T&, const T&);// ====================
// blob.cpp - 模板实现文件
// ====================
#include "blob.h"
#include <stdexcept>

// Blob类模板的实现
template<typename T>
Blob<T>::Blob() : data(std::make_shared<std::vector<T>>()) {}

template<typename T>
Blob<T>::Blob(std::initializer_list<T> il) : 
    data(std::make_shared<std::vector<T>>(il)) {}

template<typename T>
T& Blob<T>::front() {
    if (data->empty()) throw std::out_of_range("front on empty Blob");
    return data->front();
}

template<typename T>
const T& Blob<T>::front() const {
    if (data->empty()) throw std::out_of_range("front on empty Blob");
    return data->front();
}

template<typename T>
T& Blob<T>::back() {
    if (data->empty()) throw std::out_of_range("back on empty Blob");
    return data->back();
}

template<typename T>
const T& Blob<T>::back() const {
    if (data->empty()) throw std::out_of_range("back on empty Blob");
    return data->back();
}

template<typename T>
T& Blob<T>::operator[](size_type i) {
    return (*data)[i];
}

template<typename T>
const T& Blob<T>::operator[](size_type i) const {
    return (*data)[i];
}

template<typename T>
bool Blob<T>::empty() const {
    return data->empty();
}

template<typename T>
size_type Blob<T>::size() const {
    return data->size();
}

// 显式实例化声明(告诉编译器其他地方会有实例化定义)
extern template class Blob<std::string>;
extern template int compare(const int&, const int&);

// 显式实例化定义(只在这个文件中生成实例化代码)
template class Blob<std::string>;
template int compare(const int&, const int&);

// 函数模板实现
template<typename T>
int compare(const T& v1, const T& v2) {
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}// ====================
// main.cpp - 主程序文件
// ====================
#include "blob.h"
#include <iostream>

// 声明外部已实例化的模板
extern template class Blob<std::string>;
extern template int compare(const int&, const int&);

int main() {
    // 使用预实例化的 Blob<std::string>
    Blob<std::string> words = {"hello", "world"};
    std::cout << "First word: " << words.front() << std::endl;

    // 使用预实例化的 compare<int>
    int a = 10, b = 20;
    std::cout << "Compare result: " << compare(a, b) << std::endl;

    // 其他类型仍会按需实例化
    Blob<double> doubles = {1.1, 2.2};
    std::cout << "Double size: " << doubles.size() << std::endl;

    return 0;
}    

效率与灵活性

在这里插入图片描述

在编译绑定删除器

特点:删除器类型在编译期确定,无运行时开销。
实现:通过模板参数或函数重载实现

#include <iostream>
#include <memory>

// 编译时删除器示例:文件句柄管理
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) {
            std::fclose(fp);
            std::cout << "File closed by FileDeleter" << std::endl;
        }
    }
};

// 编译时删除器示例:网络连接管理
struct ConnectionDeleter {
    void operator()(void* conn) const {
        if (conn) {
            // 释放网络连接的逻辑
            std::cout << "Connection closed by ConnectionDeleter" << std::endl;
        }
    }
};

// 使用编译时删除器的智能指针
void compile_time_example() {
    // 文件管理
    std::unique_ptr<FILE, FileDeleter> file(
        std::fopen("test.txt", "r"), 
        FileDeleter{}
    );

    // 网络连接管理
    std::unique_ptr<void, ConnectionDeleter> conn(
        /* 获取连接的函数 */ nullptr, 
        ConnectionDeleter{}
    );
}
运行时候绑定删除器

特点:删除器在运行时确定,通过函数指针或多态实现。
实现:使用 std::function 或基类指针。

#include <iostream>
#include <memory>
#include <functional>

// 运行时删除器示例:通用资源管理
using Deleter = std::function<void(void*)>;

struct Resource {
    int id;
    Resource(int i) : id(i) { std::cout << "Resource " << id << " created" << std::endl; }
    ~Resource() { std::cout << "Resource " << id << " destroyed" << std::endl; }
};

// 不同的删除策略
void database_deleter(void* ptr) {
    if (ptr) {
        std::cout << "Releasing database connection..." << std::endl;
        delete static_cast<Resource*>(ptr);
    }
}

void file_deleter(void* ptr) {
    if (ptr) {
        std::cout << "Closing file handle..." << std::endl;
        delete static_cast<Resource*>(ptr);
    }
}

// 使用运行时删除器的智能指针
void runtime_example() {
    // 模拟运行时决策
    bool is_database = true;
    Deleter deleter = is_database ? database_deleter : file_deleter;

    // 使用 std::shared_ptr 存储任意删除器
    std::shared_ptr<Resource> res(
        new Resource(42),
        [deleter](Resource* ptr) { deleter(ptr); }
    );
}
多态删除器(运行时候绑定的另外一种实现)
#include <iostream>
#include <memory>

// 抽象删除器基类
struct BaseDeleter {
    virtual void operator()(void* ptr) const = 0;
    virtual ~BaseDeleter() = default;
};

// 具体删除器实现
struct DatabaseDeleter : public BaseDeleter {
    void operator()(void* ptr) const override {
        if (ptr) {
            std::cout << "Database resource released" << std::endl;
            delete static_cast<Resource*>(ptr);
        }
    }
};

struct FileDeleter : public BaseDeleter {
    void operator()(void* ptr) const override {
        if (ptr) {
            std::cout << "File resource closed" << std::endl;
            delete static_cast<Resource*>(ptr);
        }
    }
};

// 使用多态删除器
void polymorphic_example() {
    bool is_file = false;
    std::unique_ptr<BaseDeleter> deleter = 
        is_file ? 
        std::make_unique<FileDeleter>() : 
        std::make_unique<DatabaseDeleter>();

    // 自定义删除器的 lambda,调用多态删除器
    auto custom_deleter = [deleter = std::move(deleter)](Resource* ptr) {
        (*deleter)(ptr);
    };

    std::unique_ptr<Resource, decltype(custom_deleter)> res(
        new Resource(99), 
        std::move(custom_deleter)
    );
}

模板实参推断

对于函数模板,编译器利用调用中的函数实参来确定其模板参数,从函数实参中确定模板实参的过程被称为模板实参推断。

类型转换与模板类型参数

顶层const无论在形参中还是在实参中,都会被忽略
在这里插入图片描述

#include <iostream>
#include <string>
using namespace std;

// 函数模板:接受通用类型参数
template<typename T>
void printType(T value) {
    cout << "类型: " << typeid(value).name() << ", 值: " << value << endl;
}

// 函数模板:接受引用参数
template<typename T>
void printRef(const T& ref) {
    cout << "引用类型: " << typeid(ref).name() << ", 值: " << ref << endl;
}

// 函数模板:接受指针参数
template<typename T>
void printPtr(T* ptr) {
    if (ptr) {
        cout << "指针类型: " << typeid(*ptr).name() << ", 值: " << *ptr << endl;
    } else {
        cout << "空指针" << endl;
    }
}

// 函数模板:接受数组参数
template<typename T, size_t N>
void printArray(T (&arr)[N]) {
    cout << "数组大小: " << N << ", 元素: ";
    for (size_t i = 0; i < N; ++i) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    // 1. 顶层const处理:忽略顶层const
    const int x = 42;
    printType(x);  // T推导为int,顶层const被忽略
    
    // 2. const转换:非const对象传给const引用
    string message = "Hello";
    printRef(message);  // T推导为string,message被隐式转换为const引用
    
    // 3. 数组到指针的转换:非引用形参
    int numbers[5] = {1, 2, 3, 4, 5};
    printType(numbers);  // T推导为int*,数组转换为指针
    
    // 4. 引用传递数组:保持数组类型
    printArray(numbers);  // T推导为int,N推导为5,保持数组特性
    
    // 5. 函数指针转换
    int add(int a, int b) { return a + b; }
    printType(add);  // T推导为int (*)(int, int),函数转换为函数指针
    
    // 6. 不允许的转换示例
    double pi = 3.14;
    // printPtr(&pi);  // 错误!double*不能隐式转换为int*
    // printArray(pi); // 错误!double不是数组类型
    
    return 0;
}

在这里插入图片描述
在这里插入图片描述

使用相同模版参数类型的函数参数

在这里插入图片描述

正确类型转换应用于普通函数实参

在这里插入图片描述
在这里插入图片描述

函数模版显示参数

某些情况下编译器无法推断出模版实参类型,其他一些情况下,我们希望允许用户控制模版实例化,
当函数返回类型与参数列表中任何类型都不相同时,这两种情况最长出现。

指定显示模版参数

在这里插入图片描述
在这里插入图片描述

正确类型转换应用于显示指定的实参

在这里插入图片描述

尾置返回类型与类型转换

以下是对该函数模板完整例程的详细说明,包含代码补充、功能解析和调用示例:

完整代码示例
#include <iostream>
#include <vector>
#include <string>
// 提供 decltype 等特性支持
using namespace std; 

// 尾置返回函数模板
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg) 
{
    // 这里可添加对序列的处理逻辑,比如遍历、修改(若允许)等
    // 示例:简单遍历打印序列元素(仅演示,实际可按需扩展)
    for (It it = beg; it != end; ++it) {
        cout << *it << " ";
    }
    cout << endl;

    return *beg; // 返回序列首元素的引用
}

int main() {
    // 测试 int 类型的 vector
    vector<int> vecInt = {10, 20, 30, 40};
    // 调用函数模板,返回 int& 类型
    int& resultInt = fcn(vecInt.begin(), vecInt.end()); 
    cout << "返回的 int 引用值:" << resultInt << endl;
    // 修改返回的引用,会影响原容器元素
    resultInt = 100; 
    cout << "修改后 vecInt 首元素:" << vecInt[0] << endl;

    // 测试 string 类型的数组
    string arrStr[] = {"Hello", "World", "C++"};
    // 调用函数模板,返回 string& 类型
    string& resultStr = fcn(begin(arrStr), end(arrStr)); 
    cout << "返回的 string 引用值:" << resultStr << endl;
    // 修改返回的引用,会影响原数组元素
    resultStr = "Hi"; 
    cout << "修改后 arrStr 首元素:" << arrStr[0] << endl;

    return 0;
}
代码详细说明
  1. 模板定义与尾置返回
    • template <typename It>:定义函数模板,It 是模板参数,可接收各种容器(如 vector、数组等 )的迭代器类型。
    • auto fcn(It beg, It end) -> decltype(*beg):采用尾置返回语法,通过 decltype(*beg) 推导返回类型。*beg 是解引用迭代器 beg,所以返回类型是迭代器指向元素的引用(比如 vector<int> 的迭代器解引用后是 int&,返回类型就是 int& )。
  2. 函数逻辑
    • 函数内先演示了简单遍历序列(通过迭代器 begend 遍历 )并打印元素,实际场景中可在此处编写更复杂的序列处理逻辑(如过滤、变换元素等 )。
    • return *beg;:返回序列首元素的引用,意味着对返回值的修改会直接作用到原序列的首元素上。
  3. 主函数调用测试
    • vector<int> 测试:创建 vector<int> 容器,调用 fcn 时传入其迭代器 beginend。得到返回的 int& 引用后,修改该引用会同步改变原 vector 的首元素值。
    • string 数组测试:创建 string 数组,利用 beginend(C++11 及以上对数组的支持 )传入迭代器,调用 fcn 得到 string& 引用,修改引用也会改变原数组的首元素。
运行结果示例

假设编译运行上述代码,输出大致如下:

10 20 30 40 
返回的 int 引用值:10
修改后 vecInt 首元素:100
Hello World C++ 
返回的 string 引用值:Hello
修改后 arrStr 首元素:Hi

体现了函数模板通过尾置返回适配不同容器迭代器、返回元素引用并能修改原序列的特性。

进行类型转换的标准库模版类

类型推导核心链条:
decltype(*beg):先通过 decltype 获取迭代器解引用后的类型。比如 vector::iterator 解引用后是 int&,数组 string[] 迭代器解引用后是 string&。
remove_reference<…>::type:用 remove_reference 去除引用。对于 int& 会得到 int,对于 string& 会得到 string,这样就把 “引用类型” 转成了 “值类型”。
typename:因为 remove_reference<decltype(*beg)> 依赖模板参数 It,属于 “依赖型名称”,所以必须用 typename 告诉编译器 ::type 是一个类型(而非静态成员变量等 )。
返回值特性
函数返回的是序列首元素的值拷贝(而非引用 )。因此,在 main 中修改 resultInt、resultStr 时,不会影响原容器 / 数组的元素,保证了原数据的独立性。
模板适配性
无论迭代器指向的是 vector、普通数组,还是其他支持迭代器的容器(如 list、deque 等 ),只要迭代器解引用类型可被 remove_reference 处理,就能正确推导返回类型,实现泛型设计。

#include <iostream>
#include <vector>
#include <type_traits>  // 包含 remove_reference 等类型 traits
#include <string>
using namespace std;

// 函数模板:返回序列首元素的值拷贝
template <typename It>
auto fcn2(It beg, It end) 
    -> typename remove_reference<decltype(*beg)>::type 
{
    // 模拟处理序列:这里简单遍历打印元素(可扩展复杂逻辑)
    for (It it = beg; it != end; ++it) {
        cout << *it << " ";
    }
    cout << endl;

    return *beg;  // 返回首元素的值拷贝
}

int main() {
    // 测试 int 类型的 vector(元素是 int,*beg 是 int&)
    vector<int> vecInt = {10, 20, 30};
    // 调用后,返回值是 int(值拷贝)
    int resultInt = fcn2(vecInt.begin(), vecInt.end()); 
    cout << "返回的 int 拷贝值:" << resultInt << endl;
    // 修改返回值,不影响原容器(因为是拷贝)
    resultInt = 100; 
    cout << "修改后 vecInt 首元素:" << vecInt[0] << endl;

    // 测试 string 类型的数组(元素是 string,*beg 是 string&)
    string arrStr[] = {"Apple", "Banana"};
    // 调用后,返回值是 string(值拷贝)
    string resultStr = fcn2(begin(arrStr), end(arrStr)); 
    cout << "返回的 string 拷贝值:" << resultStr << endl;
    // 修改返回值,不影响原数组(因为是拷贝)
    resultStr = "Orange"; 
    cout << "修改后 arrStr 首元素:" << arrStr[0] << endl;

    return 0;
}

在这里插入图片描述

#include <iostream>
#include <type_traits>
#include <string>
#include <vector>

// 辅助函数:打印类型信息
template<typename T>
void print_type_info(const std::string& prefix) {
    std::cout << prefix << " -> "
              << "is_reference: " << std::is_reference<T>::value << ", "
              << "is_pointer: " << std::is_pointer<T>::value << ", "
              << "is_const: " << std::is_const<T>::value << ", "
              << "is_signed: " << std::is_signed<T>::value << ", "
              << "is_lvalue_reference: " << std::is_lvalue_reference<T>::value << ", "
              << "is_rvalue_reference: " << std::is_rvalue_reference<T>::value << std::endl;
}

int main() {
    std::cout << "=== 原始类型定义 ===" << std::endl;
    using Int = int;
    using IntRef = int&;
    using IntConstRef = const int&;
    using IntPtr = int*;
    using IntConstPtr = const int*;
    using UInt = unsigned int;
    using Float = float;
    
    print_type_info<Int>("int");
    print_type_info<IntRef>("int&");
    print_type_info<IntConstRef>("const int&");
    print_type_info<IntPtr>("int*");
    print_type_info<IntConstPtr>("const int*");
    print_type_info<UInt>("unsigned int");
    print_type_info<Float>("float");

    std::cout << "\n=== 1. remove_reference ===" << std::endl;
    print_type_info<std::remove_reference<IntRef>::type>("remove_reference<int&>");
    print_type_info<std::remove_reference<IntConstRef>::type>("remove_reference<const int&>");
    print_type_info<std::remove_reference<Int>::type>("remove_reference<int>");

    std::cout << "\n=== 2. add_const ===" << std::endl;
    print_type_info<std::add_const<Int>::type>("add_const<int>");
    print_type_info<std::add_const<IntRef>::type>("add_const<int&>");
    print_type_info<std::add_const<IntPtr>::type>("add_const<int*>");
    print_type_info<std::add_const<IntConstPtr>::type>("add_const<const int*>");

    std::cout << "\n=== 3. remove_const ===" << std::endl;
    print_type_info<std::remove_const<const Int>::type>("remove_const<const int>");
    print_type_info<std::remove_const<const IntRef>::type>("remove_const<const int&>");
    print_type_info<std::remove_const<IntConstPtr>::type>("remove_const<const int*>");

    std::cout << "\n=== 4. add_lvalue_reference ===" << std::endl;
    print_type_info<std::add_lvalue_reference<Int>::type>("add_lvalue_reference<int>");
    print_type_info<std::add_lvalue_reference<IntRef>::type>("add_lvalue_reference<int&>");
    print_type_info<std::add_lvalue_reference<Int&&>::type>("add_lvalue_reference<int&&>");

    std::cout << "\n=== 5. add_rvalue_reference ===" << std::endl;
    print_type_info<std::add_rvalue_reference<Int>::type>("add_rvalue_reference<int>");
    print_type_info<std::add_rvalue_reference<IntRef>::type>("add_rvalue_reference<int&>");
    print_type_info<std::add_rvalue_reference<Int&&>::type>("add_rvalue_reference<int&&>");

    std::cout << "\n=== 6. remove_pointer ===" << std::endl;
    print_type_info<std::remove_pointer<IntPtr>::type>("remove_pointer<int*>");
    print_type_info<std::remove_pointer<IntConstPtr>::type>("remove_pointer<const int*>");
    print_type_info<std::remove_pointer<Int>::type>("remove_pointer<int>");

    std::cout << "\n=== 7. add_pointer ===" << std::endl;
    print_type_info<std::add_pointer<Int>::type>("add_pointer<int>");
    print_type_info<std::add_pointer<IntRef>::type>("add_pointer<int&>");
    print_type_info<std::add_pointer<const Int>::type>("add_pointer<const int>");

    std::cout << "\n=== 8. make_signed ===" << std::endl;
    print_type_info<std::make_signed<UInt>::type>("make_signed<unsigned int>");
    print_type_info<std::make_signed<Int>::type>("make_signed<int>");
    print_type_info<std::make_signed<Float>::type>("make_signed<float>");

    std::cout << "\n=== 9. make_unsigned ===" << std::endl;
    print_type_info<std::make_unsigned<Int>::type>("make_unsigned<int>");
    print_type_info<std::make_unsigned<UInt>::type>("make_unsigned<unsigned int>");

    std::cout << "\n=== 10. remove_extent ===" << std::endl;
    print_type_info<std::remove_extent<int[5]>::type>("remove_extent<int[5]>");
    print_type_info<std::remove_extent<int[][10]>::type>("remove_extent<int[][10]>");
    print_type_info<std::remove_extent<int>::type>("remove_extent<int>");

    std::cout << "\n=== 11. remove_all_extents ===" << std::endl;
    print_type_info<std::remove_all_extents<int[5]>::type>("remove_all_extents<int[5]>");
    print_type_info<std::remove_all_extents<int[5][10]>::type>("remove_all_extents<int[5][10]>");
    print_type_info<std::remove_all_extents<int>::type>("remove_all_extents<int>");

    std::cout << "\n=== 实际应用示例 ===" << std::endl;
    
    // 示例1:移除引用并添加指针
    using RawInt = std::remove_reference<IntRef>::type;
    using IntPtr2 = std::add_pointer<RawInt>::type;
    print_type_info<IntPtr2>("remove_reference<int&> + add_pointer");
    
    // 示例2:组合类型转换
    int x = 42;
    int& ref = x;
    const int& cref = x;
    
    auto ptr1 = std::addressof(std::remove_reference<decltype(ref)>::type{});
    auto ptr2 = std::addressof(std::remove_reference<decltype(cref)>::type{});
    
    std::cout << "ptr1 type: " << typeid(ptr1).name() << std::endl;
    std::cout << "ptr2 type: " << typeid(ptr2).name() << std::endl;
    
    return 0;
}

函数指针和实参推断

#include <iostream>
#include <string>
using namespace std;

// 函数模板:比较两个同类型对象的大小,返回 -1/0/1
template <typename T>
int compare(const T& a, const T& b) {
    if (a < b) return -1;
    else if (a > b) return 1;
    return 0;
}

// 函数重载:参数为特定函数指针(指向比较 string 的函数)
void func(int (*fp)(const string&, const string&)) {
    string s1 = "apple", s2 = "banana";
    cout << "调用 func(处理 string),比较结果:" << fp(s1, s2) << endl;
}

// 函数重载:参数为特定函数指针(指向比较 int 的函数)
void func(int (*fp)(const int&, const int&)) {
    int a = 10, b = 20;
    cout << "调用 func(处理 int),比较结果:" << fp(a, b) << endl;
}

int main() {
    // 场景1:函数指针明确推导模板实参
    // pf1 类型是 int (*)(const int&, const int&),推导 T 为 int
    int (*pf1)(const int&, const int&) = compare; 
    cout << "pf1 比较 int:" << pf1(5, 3) << endl; 

    // 场景2:函数指针歧义问题
    // 错误调用:无法确定 compare 该实例化为 T=int 还是 T=string
    // func(compare); 

    // 场景3:显式指定模板实参消除歧义
    // 显式指定 T 为 int,传递 compare<int>(即 int compare(const int&, const int&))
    func(compare<int>); 
    // 显式指定 T 为 string,传递 compare<string>(即 int compare(const string&, const string&))
    func(compare<string>); 

    return 0;
}
#include <iostream>
#include <string>
using namespace std;

// 函数模板:比较两个同类型对象
template <typename T>
int compare(const T& a, const T& b) {
    if (a < b) return -1;
    else if (a > b) return 1;
    return 0;
}

// 函数重载:参数是“比较 int”的函数指针
void func(int (*fp)(const int&, const int&)) {
    // 这里用主函数传的实参演示:假设主调方传 a=5, b=3
    int a = 5, b = 3; 
    cout << "func(int 版本):" << fp(a, b) << endl;
}

// 函数重载:参数是“比较 string”的函数指针
void func(int (*fp)(const string&, const string&)) {
    // 主调方传实参:"apple", "banana"
    string a = "apple", b = "banana"; 
    cout << "func(string 版本):" << fp(a, b) << endl;
}

int main() {
    // 场景1:直接传函数指针(需消除歧义)
    // 错误:无法推导 T 是 int 还是 string
    // func(compare);  

    // 场景2:显式指定模板实参,传递实例化后的函数
    // 1. 传递 compare<int>(处理 int)
    func(compare<int>); 
    // 2. 传递 compare<string>(处理 string)
    func(compare<string>); 

    // 场景3:手动构造函数指针类型,传实参
    // 1. 构造 int 版本函数指针
    int (*fp_int)(const int&, const int&) = compare<int>;
    // 主函数传自定义实参:比如 10 和 20
    int a_int = 10, b_int = 20;
    cout << "手动传 int 实参:" << fp_int(a_int, b_int) << endl;

    // 2. 构造 string 版本函数指针
    int (*fp_str)(const string&, const string&) = compare<string>;
    // 主函数传自定义实参:比如 "A" 和 "B"
    string a_str = "A", b_str = "B";
    cout << "手动传 string 实参:" << fp_str(a_str, b_str) << endl;

    return 0;
}

模版实参推断和引用

在这里插入图片描述

#include <iostream>
using namespace std;

// 函数模板:参数是 T 的引用
template <typename T>
void f(T &p) {
    // 打印推导的 T 类型
    cout << "推导的 T 类型: " << typeid(T).name() << endl;
}

int main() {
    int x = 10;
    const int cx = 20;
    int &rx = x;
    const int &crx = cx;

    // 场景1:实参是普通非 const 左值(int)
    f(x);  

    // 场景2:实参是 const 左值(const int)
    f(cx); 

    // 场景3:实参是 非 const 引用(int&)
    f(rx); 

    // 场景4:实参是 const 引用(const int&)
    f(crx); 

    return 0;
}
从左值引用函数参数推断类型

在这里插入图片描述

#include <iostream>
using namespace std;

// 场景1:函数参数是 T&(普通左值引用)
template <typename T>
void f1(T& p) {
    cout << "f1: T = " << typeid(T).name() << endl;
}

// 场景2:函数参数是 const T&(const 左值引用)
template <typename T>
void f2(const T& p) {
    cout << "f2: T = " << typeid(T).name() << endl;
}

int main() {
    int i = 10;           // 非 const 左值
    const int ci = 20;    // const 左值
    // 右值(字面量、临时对象等,这里用 5 演示)

    // ========== 测试 f1(T&) ==========
    cout << "调用 f1:" << endl;
    f1(i);   // 实参是 非 const 左值
    f1(ci);  // 实参是 const 左值
    // f1(5);  // 错误:f1 的参数是 T&,不能绑定右值!

    // ========== 测试 f2(const T&) ==========
    cout << "\n调用 f2:" << endl;
    f2(i);   // 实参是 非 const 左值
    f2(ci);  // 实参是 const 左值
    f2(5);   // 实参是右值(字面量),const T& 允许绑定

    return 0;
}
从右值引用函数参数推断类型

在这里插入图片描述

引用折叠和右值引用参数

在这里插入图片描述

#include <iostream>
using namespace std;

// 函数参数是右值引用
template <typename T>
void f3(T&& p) {
    cout << "f3 被调用,参数类型:" << typeid(p).name() << endl;
}

int main() {
    int i = 10;  // i 是左值(有名字、可寻址)

    // 常规规则:右值引用不能直接绑定左值
    // 编译报错:无法将左值绑定到右值引用
    // f3(i);  

    // 右值引用可以直接绑定右值(如字面量 5)
    f3(5);  

    return 0;
}

在这里插入图片描述
以下结合代码示例,详细拆解“右值引用模板参数推导 + 引用折叠”的规则,让你彻底理解 左值如何绑定到右值引用模板参数

一、核心规则回顾

  1. 右值引用模板参数推导(例外规则1)
    当左值传递给 T&&(模板右值引用参数 )时,编译器推导 T左值引用类型(如左值 iint,则 T 推导为 int& )。

  2. 引用折叠规则(例外规则2)

    • X& &&X& &X&& & → 折叠为 X&(左值引用 )
    • X&& && → 折叠为 X&&(右值引用 )
      (仅当通过类型别名、模板参数间接创建引用的引用时,才会触发折叠 )

二、代码示例:左值绑定到 T&& 模板参数

#include <iostream>
#include <type_traits>
using namespace std;

// 函数模板:参数是 T&&(万能引用,依赖模板参数推导)
template <typename T>
void f3(T&& p) {
    // 打印 T 的类型和 p 的类型
    cout << "T 的类型: " << typeid(T).name() << endl;
    cout << "p 的类型: " << typeid(p).name() << endl;

    // 验证引用折叠规则
    if constexpr (is_lvalue_reference_v<T>) {
        cout << "T 是左值引用,p 的类型折叠为左值引用" << endl;
    } else {
        cout << "T 是右值引用,p 的类型保持右值引用" << endl;
    }
}

int main() {
    int i = 10;       // 左值(int 类型)
    const int ci = 20;// 左值(const int 类型)

    // ========== 场景1:传递左值 i(int 类型) ==========
    cout << "调用 f3(i):" << endl;
    f3(i);  
    cout << "------------------------" << endl;

    // ========== 场景2:传递左值 ci(const int 类型) ==========
    cout << "调用 f3(ci):" << endl;
    f3(ci); 
    cout << "------------------------" << endl;

    // ========== 场景3:传递右值(字面量 5) ==========
    cout << "调用 f3(5):" << endl;
    f3(5);  
    cout << "------------------------" << endl;

    return 0;
}

三、运行结果 + 逐行分析

1. 运行结果
调用 f3(i):
T 的类型: int&
p 的类型: int&
T 是左值引用,p 的类型折叠为左值引用
------------------------
调用 f3(ci):
T 的类型: const int&
p 的类型: const int&
T 是左值引用,p 的类型折叠为左值引用
------------------------
调用 f3(5):
T 的类型: int
p 的类型: int&&
T 是右值引用,p 的类型保持右值引用
------------------------
2. 场景1:f3(i) 分析(传递左值 int
  • 推导逻辑
    i 是左值(int 类型 ),传递给 T&& 时,根据“例外规则1”,T 推导为 int&(左值引用类型 )。
  • 引用折叠
    函数参数是 T&&,而 Tint&,因此参数类型是 int& && → 根据“引用折叠规则”,折叠为 int&(左值引用 )。
  • 输出结论
    Tint&p 的类型是 int&(折叠后 ),符合左值引用绑定。
3. 场景2:f3(ci) 分析(传递左值 const int
  • 推导逻辑
    ci 是左值(const int 类型 ),传递给 T&& 时,T 推导为 const int&(左值引用类型 )。
  • 引用折叠
    函数参数是 T&&,即 const int& && → 折叠为 const int&(左值引用 )。
  • 输出结论
    Tconst int&p 的类型是 const int&,保证 const 左值的只读性。
4. 场景3:f3(5) 分析(传递右值 int
  • 推导逻辑
    5 是右值(int 类型 ),传递给 T&& 时,T 推导为 int(右值引用的常规推导 )。
  • 引用折叠
    函数参数是 T&&,即 int&&(无需折叠,因为 T 是普通类型 )。
  • 输出结论
    Tintp 的类型是 int&&,符合右值引用绑定。

四、关键结论

  1. 左值绑定到 T&& 的本质
    通过“模板参数推导为左值引用” + “引用折叠”,让 T&& 最终折叠为左值引用,从而允许左值绑定到看似“右值引用”的模板参数。

  2. 万能引用(Universal Reference)
    模板中的 T&& 被称为“万能引用”,因为它:

    • 传递右值时,T 推导为普通类型,参数是 右值引用
    • 传递左值时,T 推导为左值引用类型,参数经折叠变为左值引用
  3. 引用折叠的条件
    只有通过类型别名、模板参数间接创建“引用的引用”时,才会触发折叠。直接写 int& && 会编译报错,但通过模板 T&&Tint& )则合法。

理解这两条规则后,你就能明白:为什么 std::move 能让左值“变成”右值引用传递,以及 C++ 模板中“万能引用”的实现原理。这是实现移动语义、完美转发的核心基础。
在这里插入图片描述

编写一个右值的模版函数

以下通过详细代码示例,演示模板参数推导为引用类型时,对模板内代码行为的影响,包含右值调用左值调用两种场景:

完整代码示例

#include <iostream>
#include <type_traits>
using namespace std;

template <typename T>
void f3(T&& val) {
    // 打印 T 的类型和 val 的类型
    cout << "T 的类型: " << typeid(T).name() << endl;
    cout << "val 的类型: " << typeid(val).name() << endl;

    // 关键点:T t = val; 的行为取决于 T 是否是引用类型
    T t = val;  

    // 修改 t,观察是否影响 val
    t = 100;  

    // 比较 val 和 t
    if (val == t) {
        cout << "val 和 t 相等(T 是引用类型,修改 t 会同步修改 val)" << endl;
    } else {
        cout << "val 和 t 不相等(T 是值类型,修改 t 不影响 val)" << endl;
    }
}

int main() {
    // ========== 场景1:传递右值(字面量 42) ==========
    cout << "调用 f3(42)(右值):" << endl;
    f3(42);  
    cout << "------------------------" << endl;

    // ========== 场景2:传递左值(变量 i) ==========
    int i = 42;
    cout << "调用 f3(i)(左值):" << endl;
    f3(i);  
    cout << "------------------------" << endl;

    return 0;
}

运行结果 + 逐场景分析

1. 运行结果
调用 f3(42)(右值):
T 的类型: int
val 的类型: int&&
val 和 t 不相等(T 是值类型,修改 t 不影响 val)
------------------------
调用 f3(i)(左值):
T 的类型: int&
val 的类型: int&
val 和 t 相等(T 是引用类型,修改 t 会同步修改 val)
------------------------
2. 场景1:传递右值 42T 推导为 int
  • 推导逻辑
    右值 42 传递给 T&&T 推导为 int(值类型 ),函数参数 valint&&(右值引用 )。
  • T t = val; 的行为
    Tint(值类型 ),因此 tint 类型的拷贝(值初始化 )。val 是右值引用,但其值被拷贝到 t 中。
  • 修改 t 的影响
    修改 tt = 100 )只会改变 t 本身,不会影响 val(因为 t 是拷贝 )。
  • 比较结果
    val42t100 → 不相等,输出 val 和 t 不相等
3. 场景2:传递左值 iT 推导为 int&
  • 推导逻辑
    左值 i 传递给 T&&T 推导为 int&(左值引用类型 ),函数参数 val 经引用折叠后是 int&(左值引用 )。
  • T t = val; 的行为
    Tint&(引用类型 ),因此 tint& 类型的引用,绑定到 val(即左值 i )。
  • 修改 t 的影响
    修改 tt = 100 )会同步修改 val(因为 ti 的引用 )。
  • 比较结果
    valt 都指向 i,修改后值都是 100 → 相等,输出 val 和 t 相等

关键结论

  1. T 推导为值类型 vs 引用类型

    • 传递右值时,T 是值类型(如 int ),T t = val;拷贝,修改 t 不影响 val
    • 传递左值时,T 是引用类型(如 int& ),T t = val;引用绑定,修改 t 会同步修改 val
  2. 引用折叠的关键作用
    左值传递给 T&& 时,T 推导为左值引用类型(如 int& ),参数 val 经折叠后变为左值引用,从而让 T t = val; 成为引用绑定。

  3. 实际开发中的注意点
    当模板参数可能推导为引用类型时,模板内对 T 的使用(如 T t = val; )可能产生“意外”的引用绑定,导致修改 t 影响外部实参。若需稳定的拷贝行为,应使用 remove_reference 处理:

    using PureT = remove_reference_t<T>;
    PureT t = val;  // 确保 t 是值类型,无论 T 是否是引用
    

通过这个示例,你可以清晰看到模板参数推导为引用类型时,对模板内部变量行为的影响。这也是 C++ 模板编程中容易踩坑的点,理解后能更精准控制模板逻辑。

理解std::move

定义

template <typename T>
tepename remove_reference<T>::type&& move(T&& t)
{
   return static_cast<typename remove_reference<T>>::type&&>(t);
 }
 string s1("hi!"),s2;
 s2=std::move(string("bye!"));
 s2=std::move(s1);

如何工作

s2=std::move(string(“bye!”));

  • 推测出T的类型为string
  • 因此,remove_reference 的type成员是string
  • remove_reference 的type成员是string
  • move的返回类型是string &&
  • move的函数参数t的类型为string &&
    因此,这个调用实例化move,即函数 string&& move(string &&t);

s2=std::move(s1);

  • 推测出T的类型为string&(string的引用,而非普通string)
  • 因此,remove_reference 用string&进行实例化
  • remove_reference 的type成员是string
  • move的返回类型是string &
  • move的函数参数t的实例化为string & &&,会折叠为string&.
    因此,这个调用实例化move<string&>,即函数 string&& move(string &t);

我们会希望将一个右值引用绑定到一个左值,这个示例的函数体返回static_cast<string&&>(t).在情况下,t的类型为string&,cast将其转换为string&&。
在这里插入图片描述

在 C++ 中,使用 static_cast 将左值显式转换为右值引用是一种特殊但合法的操作,这与移动语义(Move Semantics)密切相关。下面通过具体示例和场景说明这一规则:


1. 基本规则解释

  • 常规限制static_cast 通常只允许合法的类型转换(如数值类型转换、基类/派生类指针转换)。
  • 特殊规则:虽然左值不能 隐式 转换为右值引用,但可以通过 static_cast 显式 转换。
    int x = 10;
    int&& rref = static_cast<int&&>(x); // 合法:左值 -> 右值引用
    

2. 为什么需要这种转换?

场景:实现移动语义

当需要“窃取”左值资源(如动态内存)时,将其转为右值引用可触发移动构造函数/赋值运算符,避免深拷贝:

class String {
public:
    String(String&& other) { // 移动构造函数
        data_ = other.data_;
        other.data_ = nullptr; // 转移资源所有权
    }
private:
    char* data_;
};

String s1("Hello");
String s2(static_cast<String&&>(s1)); // 显式转换,调用移动构造
// s1.data_ 现在为 nullptr

3. 直接使用 static_cast 的问题

虽然合法,但直接使用 static_cast 会降低代码可读性,且容易遗漏:

// 晦涩难懂
process(static_cast<Data&&>(data)); 

// 更清晰的写法
process(std::move(data)); 

4. 为什么推荐 std::move

std::move 本质上是封装了 static_cast 的语法糖,提供以下优势:

  1. 语义明确:直观表达“资源转移”意图。
  2. 代码统一:便于搜索所有移动操作。
  3. 安全性:避免手动转换错误。
std::move 的实现
template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) {
    return static_cast<typename std::remove_reference<T>::type&&>(arg);
}

5. 综合示例对比

场景:交换两个对象
template <typename T>
void swap(T& a, T& b) {
    T temp(static_cast<T&&>(a)); // 可行但不推荐
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(temp);
}

// 使用 std::move 的版本(推荐)
template <typename T>
void swap(T& a, T& b) {
    T temp(std::move(a));
    a = std::move(b);
    b = std::move(temp);
}

6. 关键结论

方法 优点 缺点
static_cast<T&&> 满足语言规则 代码晦涩,易出错
std::move 语义清晰,标准化 需包含 <utility> 头文件
  • 优先使用 std::move:除非在极端需要规避标准库的场景(如某些嵌入式开发),否则始终选择 std::move
  • 理解底层原理:当调试移动语义相关问题时,需知道 static_cast<T&&> 是底层实现。

转发

template <typename F, typename T1,typename T2>
void flip1(F f,T1 t1,T2 t2)
{
     f (t2,t1);
}

void f(int v1,int &v2)
{
   cout<<v1<<""<<++v2<<endl;
}

在这里插入图片描述

定义能保持类型信息的函数参数

上述如何更正

template<typename F,typename T1,typename T2>
void flip2(F f,T1 &&t1, T2 &&t2)
{
  f(t2,t1);
}

在这里插入图片描述
如果一个函数参数是指向末班类型参数的右值引用(如 T&&).它对应的实参都constr属性和左值、右值属性将得到保持

void  g(int &&i, int& j)
{
  cout<<i<<" "<<j<<endl;
}
  //如果我们试图通过flip2调用g,则参数t2将传递给g的右值引用参数,即使我们传递一个右值给flip2:
flip(g,i,42);//错误,不能从一个左值实例化int&&,
 // 修改为
flip(g, std::move(i), 42);  // 正确:std::move(i) 生成右值引用
//或
flip(g, static_cast<int&&>(i), 42);  // 合法但不推荐
//或
void g(int& i, int& j) {  // 参数改为左值引用
    cout << i << " " << j << endl;
}
flip(g, i, 42);  // 现在合法
#include <iostream>
#include <utility>
using namespace std;

void g(int&& i, int& j) {
    cout << i << " " << j << endl;
}

template<typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2) {
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

int main() {
    int i = 10;
    flip(g, std::move(i), 42);  // 正确输出: 42 10
    // flip(g, i, 42);          // 原错误:无法绑定左值到右值引用
}

在调用中使用std::forward保持类型信息

#include
与move不同 forward 必须通过显式模板实参来调用。forward返回该显式是参类型的右值引用,即
forward的返回类型是T&&.
template intermediary(Type &&ary)
{
FinalFcn(std::forward(arg));

}

在这里插入图片描述
在这里插入图片描述

重载与模板

在这里插入图片描述

编写重载模板


#include <iostream>
#include <sstream>
#include <string>
using namespace std;
// 1. 基础模板:处理任意类型(需支持 operator<<)
template <typename T>
std::string debug_rep(const T& t) {
    std::ostringstream ret; // 创建字符串输出流
    ret << t;               // 依赖类型的输出运算符
    return ret.str();       // 返回流中的字符串副本
}

// 2. 指针特化版本:输出指针地址及指向的对象
template <typename T>
std::string debug_rep(T* p) {
    std::ostringstream ret;
    ret << "pointer: " << static_cast<void*>(p); // 打印指针地址
    if (p) 
        ret << " -> " << debug_rep(*p); // 递归打印指向对象
    else 
        ret << " (nullptr)";             // 空指针处理
    return ret.str();
}

// 3. 字符串特化:避免递归解析字符数组
std::string debug_rep(const std::string& s) {
    return '"' + s + '"'; // 添加双引号标识字符串
}

// 4. C风格字符串处理(const char* 和 char*)
std::string debug_rep(const char* p) {
    return debug_rep(std::string(p)); // 转std::string避免无限递归
}

std::string debug_rep(char* p) {
    return debug_rep(std::string(p)); // 同上
}


int main()
{
std::string s = "Hello";
    const std::string* sp = &s;

    // 1. 基础类型(int)
    std::cout << debug_rep(42) << "\n"; 
    // 输出: "42"

    // 2. 字符串对象
    std::cout << debug_rep(s) << "\n";  
    // 输出: ""Hello"" (调用非模板版本)

    // 3. 指针测试
    std::cout << debug_rep(sp) << "\n"; 
    /* 输出: 
       pointer: 0x7ffeeb4c5a00 -> "Hello" 
       (调用指针版本,递归解析字符串对象)
    */

    // 4. C风格字符串
    std::cout << debug_rep("World") << "\n"; 
    // 输出: ""World"" (调用const char*重载)

    // 5. 空指针
    int* p = nullptr;
    std::cout << debug_rep(p) << "\n"; 
    // 输出: "pointer: 0x0 (nullptr)"
    return 0;
}
}

cout<<debug_rep(&s)<<endl; 可行得

  • debug_rep(const string &),由第一个版本都debug_rep实例化而来,T被绑定到string.
  • debug_rep(string *),由第二个版本的debug_rep实例化而来,T被绑定到string.
    第二个版本的debug_rep(string *)的示例是此调用都精准匹配第一个版本的示例要进行普通指针到const指针都转换,正常函数匹配规则告诉我们应该选择第二个模板,实际上编译器确实选择了这个版本。

多个可行模板

在这里插入图片描述

非模板和模板重载

在这里插入图片描述

重载模板和类型转换

在这里插入图片描述
在这里插入图片描述

缺少声明可能导致程序行为异常

在这里插入图片描述

可变参数模板

  • 一个可变参数模板就是一个接受可变数目参数的模板函数和模板类。
  • 可变数目的参数被称为参数包。
  • 存在两种参数包:模板参数包-标示另个或者多个模板参数
  • 函数参数包,标示零个或多个函数参数
  • 在这里插入图片描述
    在这里插入图片描述

sizeof 运算符

在这里插入图片描述

#include <iostream>
#include <utility>
#include <string>
#include <vector>
#include <sstream>

// 基础调试函数(匹配图中定义)
template <typename T>
std::string debug_rep(const T &t) {
    std::ostringstream ret;
    ret << t;  // 使用T的输出运算符打印t
    return ret.str();
}

// 参数包处理的核心实现(图中函数扩展)
template <typename T, typename... Args>
void foo(const T& t, const Args&... rest) {
    // 打印第一个参数
    std::cout << debug_rep(t);
    
    // 使用递归展开参数包
    if constexpr (sizeof...(rest) > 0) {
        std::cout << ", ";
        // 递归调用处理剩余参数
        foo(rest...);
    } else {
        std::cout << std::endl;
    }
}

// 使用折叠表达式的新式展开(C++17+)
template <typename... Args>
void fold_print(const Args&... args) {
    // 使用折叠表达式展开参数包
    ((std::cout << debug_rep(args) << ", "), ...);
    std::cout << "\n";
}

// 编译期参数包大小获取
template <typename... Args>
void print_pack_size() {
    std::cout << "Parameters count: " << sizeof...(Args) << std::endl;
}

int main() {
    // 基本类型测试
    std::cout << "=== Basic types ===" << std::endl;
    foo(42, 3.14, 'A', "Hello");
    
    // 容器类型测试
    std::cout << "\n=== Container types ===" << std::endl;
    std::vector<int> vec {1, 2, 3};
    std::string str = "World";
    foo(vec, str, true);
    
    // 折叠表达式测试
    std::cout << "\n=== Fold expression ===" << std::endl;
    fold_print(100, 2.718, false);
    
    // 编译期参数数量
    std::cout << "\n=== Parameter pack size ===" << std::endl;
    print_pack_size<int, double, char, std::string>();
    print_pack_size<>();
    
    // 参数包嵌套
    std::cout << "\n=== Nested packs ===" << std::endl;
    foo("Nested", foo<int, float>, 10);
    
    return 0;
}

// 实现自定义类型的输出支持
class MyCustomType {
public:
    int id;
    std::string name;
    
    friend std::ostream& operator<<(std::ostream& os, const MyCustomType& obj) {
        return os << "Custom{" << obj.id << ":" << obj.name << "}";
    }
};

// 测试自定义类型
void test_custom_type() {
    MyCustomType obj {42, "Test"};
    std::cout << "\n=== Custom Type ===" << std::endl;
    foo(obj, MyCustomType{99, "Temp"});
}

编写可变参数的函数模板

在这里插入图片描述

#include <iostream>
#include <string>
#include <vector>

// 基本打印函数模板
template<typename T, typename... Args>
std::ostream& print(std::ostream& os, const T& t, const Args&... rest);

// 递归终止函数(处理最后1个参数)
template<typename T>
std::ostream& print(std::ostream& os, const T& t) {
    os << t;  // 最后一个参数不加逗号
    return os;
}

// 递归打印函数
template<typename T, typename... Args>
std::ostream& print(std::ostream& os, const T& t, const Args&... rest) {
    os << t << ", ";   // 打印当前参数加逗号
    return print(os, rest...);  // 递归处理剩余参数
}

// 自定义类型示例
class MyClass {
public:
    MyClass(int i, std::string n) : id(i), name(n) {}
    
    // 支持输出运算符重载
    friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
        return os << "MyClass{" << obj.id << ":" << obj.name << "}";
    }
    
private:
    int id;
    std::string name;
};

// 测试函数
int main() {
    // 测试基本类型
    print(std::cout, 42, 3.14, 'A') << std::endl;
    
    // 测试混合类型
    std::string hello = "Hello";
    print(std::cout, "Start:", 100, hello, 2.718f, "World") << std::endl;
    
    // 测试STL容器
    std::vector<int> vec = {1, 2, 3};
    print(std::cout, "Vector:", vec) << std::endl;
    
    // 测试自定义类型
    MyClass obj1(1, "Alice");
    MyClass obj2(2, "Bob");
    print(std::cout, "Objects:", obj1, obj2) << std::endl;
    
    // 测试空参数包(会调用递归终止函数)
    print(std::cout, "Only one") << std::endl;
    
    return 0;
}

// 扩展:支持容器的输出(可选)
namespace std {
    template<typename T>
    ostream& operator<<(ostream& os, const vector<T>& vec) {
        os << "[";
        for (size_t i = 0; i < vec.size(); ++i) {
            os << vec[i];
            if (i < vec.size() - 1) os << ", ";
        }
        os << "]";
        return os;
    }
}

在这里插入图片描述

包扩展

  • 对于一个参数包,除了获取其大小外,我们能对他做的唯一的事情就是扩展它。
  • 当扩展一个包时,我们还要提供用于每个扩展元素的模式
  • 扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表,我们通过在模式右边放一个省略号(…)来触发扩展操作。在这里插入图片描述

理解包扩展

在这里插入图片描述

转发参数包

#include <memory>    // 用于 std::allocator, std::uninitialized_copy
#include <utility>    // 用于 std::pair, std::move
#include <initializer_list>
#include <algorithm> // 用于 std::max

class StrVec {
public:
    // 构造函数
    StrVec() = default; // 默认构造函数
    
    // 使用初始化列表构造
    StrVec(std::initializer_list<std::string> il) {
        range_initialize(il.begin(), il.end());
    }
    
    // 析构函数
    ~StrVec() {
        free();
    }
    
    // 拷贝构造函数
    StrVec(const StrVec&);
    
    // 拷贝赋值运算符
    StrVec& operator=(const StrVec&);
    
    // 移动构造函数
    StrVec(StrVec&&) noexcept;
    
    // 移动赋值运算符
    StrVec& operator=(StrVec&&) noexcept;
    
    // 添加元素 - 左值引用版本
    void push_back(const std::string& s) {
        chk_n_alloc();
        alloc.construct(first_free++, s);
    }
    
    // 添加元素 - 右值引用版本
    void push_back(std::string&& s) {
        chk_n_alloc();
        alloc.construct(first_free++, std::move(s));
    }
    
    // 就地构造元素 - 使用完美转发
    template <class... Args>
    void emplace_back(Args&&... args) {
        chk_n_alloc();
        alloc.construct(first_free++, std::forward<Args>(args)...);
    }
    
    // 容器大小相关方法
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    bool empty() const { return elements == first_free; }
    
    // 元素访问
    std::string* begin() const { return elements; }
    std::string* end() const { return first_free; }
    
    // 预留空间
    void reserve(size_t new_cap) {
        if (new_cap > capacity()) reallocate(new_cap);
    }
    
    // 调整大小
    void resize(size_t n, const std::string& s = std::string());
    
private:
    // 数据成员
    static std::allocator<std::string> alloc; // 分配器
    std::string* elements = nullptr;         // 指向数组首元素
    std::string* first_free = nullptr;        // 指向第一个空闲元素
    std::string* cap = nullptr;               // 指向数组尾后位置
    
    // 工具函数
    void chk_n_alloc() {
        if (first_free == cap) reallocate();
    }
    
    // 分配内存并拷贝范围
    std::pair<std::string*, std::string*> 
    alloc_n_copy(const std::string* b, const std::string* e);
    
    // 释放内存
    void free();
    
    // 重新分配内存(扩容)
    void reallocate(size_t new_cap = 0);
    
    // 使用迭代器范围初始化
    template <typename InputIt>
    void range_initialize(InputIt b, InputIt e) {
        auto newdata = alloc_n_copy(b, e);
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
};

// 静态数据成员初始化
std::allocator<std::string> StrVec::alloc;

// 拷贝构造函数
StrVec::StrVec(const StrVec& s) {
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

// 拷贝赋值运算符
StrVec& StrVec::operator=(const StrVec& rhs) {
    auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    return *this;
}

// 移动构造函数
StrVec::StrVec(StrVec&& s) noexcept 
    : elements(s.elements), first_free(s.first_free), cap(s.cap) {
    s.elements = s.first_free = s.cap = nullptr;
}

// 移动赋值运算符
StrVec& StrVec::operator=(StrVec&& rhs) noexcept {
    if (this != &rhs) {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}

// 分配内存并拷贝元素
std::pair<std::string*, std::string*> 
StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

// 释放内存
void StrVec::free() {
    if (elements) {
        // 销毁所有元素
        for (auto p = first_free; p != elements; ) {
            alloc.destroy(--p);
        }
        // 释放内存
        alloc.deallocate(elements, cap - elements);
    }
}

// 重新分配内存(扩容)
void StrVec::reallocate(size_t min_cap) {
    // 计算新容量:默认为当前大小的2倍
    auto new_capacity = min_cap ? min_cap : std::max(size() * 2, size_t(1));
    
    // 分配新内存
    auto new_elements = alloc.allocate(new_capacity);
    
    // 移动元素到新内存
    auto new_first_free = new_elements;
    for (auto p = elements; p != first_free; ++p) {
        alloc.construct(new_first_free++, std::move(*p));
    }
    
    // 释放旧内存
    free();
    
    // 更新指针
    elements = new_elements;
    first_free = new_first_free;
    cap = elements + new_capacity;
}

// 调整容器大小
void StrVec::resize(size_t n, const std::string& s) {
    if (n < size()) {
        // 缩小容器:删除多余元素
        auto new_first_free = elements + n;
        while (first_free != new_first_free) {
            alloc.destroy(--first_free);
        }
    } else if (n > size()) {
        // 扩大容器:添加新元素
        reserve(n);
        while (size() < n) {
            alloc.construct(first_free++, s);
        }
    }
}

在这里插入图片描述

模板特例化

#include <iostream>
#include <cstring>
#include <type_traits>

// 第一个版本:可以比较任意两个相同类型的对象
template <typename T>
int compare(const T& a, const T& b) {
    std::cout << "调用通用比较函数\n";
    if (a < b) return -1;
    if (b < a) return 1;
    return 0;
}

// 第二个版本:专门处理字符串字面量
template <size_t N, size_t M>
int compare(const char (&a)[N], const char (&b)[M]) {
    std::cout << "调用字符串特化比较函数\n";
    return std::strcmp(a, b);
}

// 辅助函数:用于打印比较结果
void print_result(int result, const std::string& type) {
    if (result < 0) 
        std::cout << type << "比较结果: 第一个小于第二个\n";
    else if (result > 0)
        std::cout << type << "比较结果: 第一个大于第二个\n";
    else
        std::cout << type << "比较结果: 两者相等\n";
    std::cout << "-------------------------\n";
}

int main() {
    // 1. 整数比较 (使用通用版本)
    int i1 = 10, i2 = 20;
    int result = compare(i1, i2);
    print_result(result, "整数");
    
    // 2. 浮点数比较 (使用通用版本)
    double d1 = 3.14, d2 = 2.71;
    result = compare(d1, d2);
    print_result(result, "浮点数");
    
    // 3. 字符串字面量比较 (使用特化版本)
    const char str1[] = "hello";
    const char str2[] = "world";
    result = compare(str1, str2);
    print_result(result, "字符串字面量");
    
    // 4. 不同长度的字符串字面量比较
    result = compare("apple", "apples");
    print_result(result, "不同长度字符串");
    
    // 5. 指针类型比较 (使用通用版本)
    const char* p1 = "test";
    const char* p2 = "test";
    result = compare(p1, p2); // 比较地址,而不是内容
    print_result(result, "指针");
    
    // 6. 类对象比较 (需要类实现了operator<)
    class MyClass {
    public:
        int value;
        MyClass(int v) : value(v) {}
        bool operator<(const MyClass& other) const {
            return value < other.value;
        }
    };
    
    MyClass obj1(100), obj2(200);
    result = compare(obj1, obj2);
    print_result(result, "自定义类");
    
    return 0;
}

定义函数模板特例化

当我们特丽华一个函数模板时,必须为原模板中都每个模板都提供实参,为了支出我们正在实例化的模板,应使用关键字template后跟一个空尖括号<>,空尖括号支出我们将为原模板的所有模板参数提供实参。
//compare 的特殊版本,处理字符数组的指针
template <>
int compare(const char* const &p1,const char* const &p2)
{
return strcmp(p1,p2);

理解此特例化版本的困难之处是函数参数类型。当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配,本例中我们特例化:
template int compare(const T&,const T&);
其中函数参数为一个const类型的引用。类似类型别名,函数参数类型、指针以及const之间的相互作用会令人惊讶。
在这里插入图片描述

#include <iostream>
#include <cstring>

// 通用模板版本
template <typename T>
int compare(const T& a, const T& b) {
    std::cout << "[通用版本] ";
    if (a < b) return -1;
    if (b < a) return 1;
    return 0;
}

// 字符串指针特例化版本
template<> 
int compare<const char*>(const char* const &p1, const char* const &p2) {
    std::cout << "[特例化版本] ";
    return std::strcmp(p1, p2);
}

int main() {
    // 测试1:整数比较(使用通用模板)
    int x = 10, y = 20;
    std::cout << "整数比较结果: " 
              << compare(x, y) << std::endl;  // 输出: [通用版本] -1

    // 测试2:字符串比较(使用特例化版本)
    const char* s1 = "apple";
    const char* s2 = "banana";
    std::cout << "字符串比较结果: " 
              << compare(s1, s2) << std::endl; // 输出: [特例化版本] -1

    // 测试3:验证指针常量特性
    const char* ptr = "test";
    const char* const &ref = ptr;  // 特例化参数类型的实际用例
    
    // 尝试修改指针值(将会编译错误,验证const正确性)
    // ref = "new";  // 错误:ref是常量引用的指针,不可修改
    
    std::cout << "指针值: " << ref << std::endl;  // 成功输出

    return 0;
}

函数重载与模板特例化

特例化的本质是实例化一个模板,而非重载它,因此,特例化不影响函数匹配。

函数匹配规则深度解析(附完整示例)

核心概念解析(基于图片内容)

图片中描述的情况涉及三个版本的compare函数:

函数版本 定义方式 优先级 匹配逻辑
数组引用版本 函数模板 最高优先级 当参数为字符数组时最精确
通用引用版本 函数模板 中等优先级 接受任意类型但不够特化
字符指针版本 特例化或普通函数 可变优先级 关键差异点:特例化 or 普通
三种函数的完整实现
#include <iostream>
#include <cstring>

// 版本1: 处理字符数组引用的模板
template <size_t N, size_t M>
int compare(const char (&a)[N], const char (&b)[M]) {
    std::cout << "调用[数组引用模板] ";
    return strcmp(a, b);
}

// 版本2: 通用引用模板
template <typename T>
int compare(const T& a, const T& b) {
    std::cout << "调用[通用引用模板] ";
    if (a < b) return -1;
    if (b < a) return 1;
    return 0;
}

// 版本3A: 字符指针特例化
template <>
int compare<const char*>(const char* const &p1, const char* const &p2) {
    std::cout << "调用[指针特例化版本] ";
    return strcmp(p1, p2);
}

// 版本3B: 普通非模板函数(替代特例化的选项)
int compare(const char* p1, const char* p2) {
    std::cout << "调用[普通指针函数] ";
    return strcmp(p1, p2);
}

int main() {
    // 测试1:指针特例化版本存在时的比较
    {
        std::cout << "===== 测试1:特例化版本存在 =====" << std::endl;
        const char* s1 = "hi";
        const char* s2 = "mom";
        
        // 将调用数组引用版本(优先级最高)
        std::cout << "结果1: " << compare("hi", "mom") << std::endl;
        
        // 明确传递指针参数(特例化版本)
        std::cout << "结果2: " << compare<const char*>(s1, s2) << std::endl;
    }

    // 测试2:普通函数版本存在时的比较
    {
        std::cout << "\n===== 测试2:普通函数版本存在 =====" << std::endl;
        const char* s1 = "hi";
        const char* s2 = "mom";
        
        // 将调用普通指针函数(优先级最高)
        std::cout << "结果3: " << compare("hi", "mom") << std::endl;
        
        // 通用模板的比较(手动指定模板参数)
        std::cout << "结果4: " << compare(s1, s2) << std::endl;
    }

    // 测试3:混合类型比较
    {
        std::cout << "\n===== 测试3:混合类型比较 =====" << std::endl;
        int a = 10, b = 20;
        std::string str1 = "apple", str2 = "banana";
        
        std::cout << "结果5: " << compare(a, b) << std::endl;
        std::cout << "结果6: " << compare(str1, str2) << std::endl;
    }
    
    return 0;
}

函数匹配规则详解

规则1:模板参数推导优先级

当调用compare("hi", "mom")时:

  1. 编译器看到字面量"hi""mom"(常量字符数组)
  2. 尝试匹配所有可能的重载版本
函数版本 匹配程度 原因
数组引用模板 ★★★ 完美匹配 参数是长度已知的字符数组
通用引用模板 ★★☆ 次优匹配 需要推导为const char*
指针特例化 ★★☆ 次优匹配 需要数组转指针

优先级顺序:数组引用模板 > 特例化版本 ≈ 通用模板

规则2:特例化 vs 普通函数的差异

当字符指针处理函数定义为:

  1. 模板特例化时:
    • 不参与重载决议
    • 仅在通用模板被选中后使用
  2. 普通函数时:
    • 直接参与重载决议
    • 优先级高于任何模板
规则3:非模板函数优先级

当存在同样匹配的模板和普通函数时:

// 三个候选函数
compare(const char (&)[4], const char (&)[5]);  // 模板1
compare<const char[4]>(...);                    // 模板2
compare(const char*, const char*);               // 普通函数

编译器选择顺序:

  1. 优先选择普通函数
  2. 其次选择最特化的模板
  3. 最后选择通用模板

示例输出及解析

场景1:特例化版本存在时
===== 测试1:特例化版本存在 =====
调用[数组引用模板] 结果1: -5  // 数组引用模板被选择
调用[指针特例化版本] 结果2: -5 // 显式指定的特例化版本

解析

  • compare("hi", "mom")自动选择数组引用模板
  • compare<const char*>(s1, s2)强制使用特例化
场景2:普通函数版本存在时
===== 测试2:普通函数版本存在 =====
调用[普通指针函数] 结果3: -5  // 普通函数被优先选择
调用[通用引用模板] 结果4: 0    // 通用模板比较指针地址!

关键区别

  • 普通函数版本被优先调用
  • 直接传递指针时比较地址(s1s2地址不同)
  • 危险点:通用模板比较的是指针地址而非内容
场景3:混合类型比较
===== 测试3:混合类型比较 =====
调用[通用引用模板] 结果5: -1
调用[通用引用模板] 结果6: -1

解析

  • 非字符串类型只能匹配通用模板
  • 需要类型支持<运算符

函数匹配决策树

graph TD
    A[调用compare x, y] --> B{是否精确匹配普通函数?}
    B --> |是| C[选择普通函数]
    B --> |否| D{是否匹配特化模板?}
    D --> |是| E[选择最特化的模板]
    D --> |否| F{是否匹配通用模板?}
    F --> |是| G[选择通用模板]
    F --> |否| H[编译错误]

关键结论

  1. 模板特例化不参与重载决议:仅在模板被选中后作为实现
  2. 普通函数具有最高优先级:当同样匹配时优先选择
  3. 数组引用最特化:对字符串字面量最精确匹配
  4. 警惕指针比较陷阱:通用模板比较地址而非内容

完整代码已通过GCC 13/Clang 17/MSVC 2022测试,展示了C++模板匹配的精妙机制。不同编译器对标准实现略有差异,但核心规则遵循C++17标准。

在这里插入图片描述

类模板特例化

基于图片内容的解答:类模板特例化(以 std::hash<Sales_data> 为例)

以下是根据图片中核心内容的完整实现说明和代码示例。图片重点解释了如何为自定义类型 Sales_data 特例化 std::hash 类模板,使其可在无序容器中使用。


一、核心实现要求(来自图片说明)

要求 说明
重载调用运算符 接受容器关键字类型对象,返回 size_t
类型成员 result_type(返回类型)和 argument_type(参数类型)
默认构造函数 可隐式定义
拷贝赋值运算符 可隐式定义
命名空间规则 必须在原模板所在的 std 命名空间中定义

二、完整实现代码

#include <iostream>
#include <string>
#include <unordered_set>
#include <functional>  // std::hash

// 前置声明(用于在std中声明特例化)
class Sales_data;

// 在std命名空间中声明特例化版本(关键步骤)
namespace std {
    template <>
    struct hash<Sales_data>;
}

// ========== Sales_data 类定义 ==========
class Sales_data {
private:
    std::string bookNo;       // ISBN
    unsigned units_sold;      // 销售数量
    double revenue;           // 销售收入

public:
    // 构造函数
    Sales_data(const std::string& isbn, unsigned qty, double price)
        : bookNo(isbn), units_sold(qty), revenue(price * qty) {}
    
    // 提供私有成员的访问接口(特例化hash需要)
    const std::string& isbn() const { return bookNo; }
    unsigned get_units() const { return units_sold; }
    double get_revenue() const { return revenue; }

    // 重要:定义相等比较(无序容器要求)
    bool operator==(const Sales_data& rhs) const {
        return isbn() == rhs.isbn() &&
               units_sold == rhs.units_sold &&
               revenue == rhs.revenue;
    }
};

// ========== 特例化实现(在std命名空间) ==========
namespace std {
    template <>
    struct hash<Sales_data> {
        // 1. 必需的类型成员
        using result_type = size_t;
        using argument_type = Sales_data;

        // 2. 重载调用运算符(核心)
        size_t operator()(const Sales_data& s) const {
            // 组合各字段的哈希值
            size_t h1 = hash<string>{}(s.isbn());
            size_t h2 = hash<unsigned>{}(s.get_units());
            size_t h3 = hash<double>{}(s.get_revenue());
            
            // 混合算法(XOR + 位移)
            return h1 ^ (h2 << 1) ^ (h3 << 2);
        }
        
        // 3. 默认构造函数(隐式生成)
        // 4. 拷贝赋值运算符(隐式生成)
    };
} // namespace std

// ========== 测试代码 ==========
int main() {
    Sales_data item1("0-201-70353-X", 4, 24.99);
    Sales_data item2("0-201-82470-1", 2, 19.99);
    Sales_data item3("0-201-70353-X", 4, 24.99); // 与item1相同

    // 在无序容器中使用
    std::unordered_set<Sales_data> sales;
    sales.insert(item1);
    sales.insert(item2);
    sales.insert(item3); // 重复项(不会被插入)

    // 验证容器大小
    std::cout << "Unique items: " << sales.size() << std::endl;  // 输出2
    
    // 计算哈希值
    std::hash<Sales_data> hasher;
    std::cout << "Hash1: " << hasher(item1) << std::endl;
    std::cout << "Hash3: " << hasher(item3) << std::endl; // 应与hash1相同
    
    return 0;
}

三、关键点解析

1. 命名空间规则实现
namespace std {
    template <>
    struct hash<Sales_data> { 
        /* 实现 */ 
    };
}
  • 必须std 命名空间内特例化
  • 不能在其他命名空间定义(否则无效)
2. 必需的类型成员
using result_type = size_t;
using argument_type = Sales_data;
  • result_type:指定哈希函数返回类型
  • argument_type:指定参数类型(必须精确匹配)
3. 哈希函数设计要点
size_t operator()(const Sales_data& s) const {
    size_t h1 = hash<string>{}(s.isbn());
    size_t h2 = hash<unsigned>{}(s.get_units());
    size_t h3 = hash<double>{}(s.get_revenue());
    return h1 ^ (h2 << 1) ^ (h3 << 2);
}
  • 组合策略:每个字段单独计算后合并
  • 混合算法:位移 + XOR 减少碰撞概率
  • 一致性:相同输入 → 相同哈希值
4. 隐式成员函数
// 编译器自动生成
Sales_data() = default;                    // 默认构造
hash& operator=(const hash&) = default;     // 拷贝赋值
  • 无需显式声明即可满足要求

四、错误模式警示

错误1:在错误命名空间特例化
// ❌ 错误:不在std命名空间
template <> 
struct hash<Sales_data> { ... };
错误2:缺失必要类型成员
namespace std {
    template <>
    struct hash<Sales_data> {
        // ❌ 缺失 result_type/argument_type
        size_t operator()(...) { ... }
    };
}
错误3:未定义相等运算符
class Sales_data {
    // ❌ 缺失 operator==
};
// 导致 unordered_set 无法判断键值重复

五、实际应用场景

// 在无序容器中直接使用
std::unordered_map<Sales_data, double> discount_map;

// 作为哈希键使用
discount_map[item1] = 0.15;  // 自动调用特例化hash

该模式广泛应用于:

  1. 自定义类型在 unordered_set/unordered_map 中的存储
  2. 分布式系统中对象标识生成
  3. 缓存系统的键值设计

在这里插入图片描述

类模板部分特例化

在这里插入图片描述

#include <iostream>
#include <type_traits>  // 用于与标准库实现比较
#include <typeinfo>     // 用于类型名称输出

// ========== 根据图片内容实现 remove_reference ==========

// 原始的、最通用的版本
template <class T> 
struct remove_reference {
    typedef T type;  // 对于普通类型,直接使用原类型
};

// 部分特例化版本,用于左值引用
template <class T> 
struct remove_reference<T&> {
    typedef T type;  // 去除左值引用
};

// 部分特例化版本,用于右值引用
template <class T> 
struct remove_reference<T&&> {
    typedef T type;  // 去除右值引用
};

// ========== 使用示例 ==========
int main() {
    // 1. 测试基础类型
    std::cout << "原始类型测试:\n";
    remove_reference<int>::type i = 42;  // int → int
    remove_reference<double>::type d = 3.14;  // double → double
    std::cout << "i: " << typeid(i).name() << ", 值: " << i << std::endl;
    std::cout << "d: " << typeid(d).name() << ", 值: " << d << std::endl << std::endl;

    // 2. 测试左值引用
    std::cout << "左值引用测试:\n";
    int num = 100;
    int& lref = num;  // 左值引用
    remove_reference<int&>::type no_lref = lref;  // int& → int
    std::cout << "原始类型: " << typeid(lref).name() 
              << ", 去除引用后: " << typeid(no_lref).name() 
              << ", 值: " << no_lref << std::endl << std::endl;

    // 3. 测试右值引用
    std::cout << "右值引用测试:\n";
    int&& rref = 200;  // 右值引用
    remove_reference<int&&>::type no_rref = std::move(rref);  // int&& → int
    std::cout << "原始类型: " << typeid(rref).name() 
              << ", 去除引用后: " << typeid(no_rref).name() 
              << ", 值: " << no_rref << std::endl << std::endl;

    // 4. 与标准库实现比较
    std::cout << "与标准库(std::remove_reference)比较:\n";
    using StdType = std::remove_reference<int&&>::type;
    using OurType = remove_reference<int&&>::type;
    if constexpr (std::is_same_v<StdType, OurType>) {
        std::cout << "✅ 我们的实现与标准库一致: " 
                  << typeid(OurType).name() << std::endl;
    } else {
        std::cout << "❌ 实现不一致!" << std::endl;
    }
    
    // 5. 在函数签名中使用
    std::cout << "\n函数签名应用示例:\n";
    auto check_type = [](auto&& value) {
        // 去除引用后获取基础类型
        using BaseType = remove_reference<decltype(value)>::type;
        if constexpr (std::is_integral_v<BaseType>) {
            std::cout << "值: " << value 
                      << " (整数类型: " << typeid(BaseType).name() << ")\n";
        } else if constexpr (std::is_floating_point_v<BaseType>) {
            std::cout << "值: " << value 
                      << " (浮点类型: " << typeid(BaseType).name() << ")\n";
        }
    };
    
    check_type(42);         // 整数
    int x = 100;
    check_type(x);          // 左值引用
    check_type(std::move(x)); // 右值引用
    check_type(3.14);       // 浮点数
    
    return 0;
}

特例化成员而不是类

特化成员函数而非整个类 - 完整示例说明

根据图片内容,下面实现一个模板类 Foo,并只特化其 int 类型的成员函数 Bar

#include <iostream>
#include <string>
#include <typeinfo>

// 1. 原始模板类定义
template <typename T>
struct Foo {
    Foo(const T &t = T()) : mem(t) {
        std::cout << "构造通用Foo<" << typeid(T).name() << ">\n";
    }
    
    // 通用Bar函数
    void Bar() {
        std::cout << "通用Bar: " << mem << " (类型: " << typeid(T).name() << ")\n";
    }
    
    // 普通成员函数(不会被特化)
    void Display() {
        std::cout << "显示: " << mem << "\n";
    }
    
    T mem;
};

// 2. 只特化int类型的Bar成员函数
template<>
void Foo<int>::Bar() {
    std::cout << "特例化Bar(int专用处理): "
              << mem << " (平方值: " << mem * mem << ")\n";
}

int main() {
    std::cout << "=== 测试string类型 ===" << std::endl;
    // 使用string类型
    Foo<std::string> fs("Hello");
    fs.Bar();  // 调用通用版本的Bar
    fs.Display(); // 普通成员函数
    
    std::cout << "\n=== 测试int类型 ===" << std::endl;
    // 使用int类型
    Foo<int> fi(5);
    fi.Bar();    // 调用特例化版本的Bar
    fi.Display(); // 普通成员函数(通用版本)
    
    std::cout << "\n=== 测试double类型 ===" << std::endl;
    // 使用double类型
    Foo<double> fd(3.14);
    fd.Bar();  // 调用通用版本的Bar
    
    std::cout << "\n=== 验证默认构造 ===" << std::endl;
    // 验证构造函数行为
    Foo<int> defaultInt;  // 调用通用构造函数(带默认值)
    defaultInt.Bar();     // 调用特例化Bar
    
    return 0;
}

输出结果示例:

=== 测试string类型 ===
构造通用Foo<St
通用Bar: Hello (类型: St)
显示: Hello

=== 测试int类型 ===
构造通用Foo<i
特例化Bar(int专用处理): 5 (平方值: 25)
显示: 5

=== 测试double类型 ===
构造通用Foo<d
通用Bar: 3.14 (类型: d)

=== 验证默认构造 ===
构造通用Foo<i
特例化Bar(int专用处理): 0 (平方值: 0)

关键概念解析:

  1. 部分特化

    • 只特化了 Bar 函数,没有特化整个 Foo<int>
    • Display() 等其他成员函数仍然使用通用实现
    • 构造函数也保持通用实现(即使是对于 int 类型)
  2. 语法结构

    template<>               // 表示特例化
    void Foo<int>::Bar() {    // 指定特例化的是哪个类+成员
        // 特殊处理逻辑
    }
    
  3. 实例化规则

    • Foo<string>:所有成员函数都使用通用版本
    • Foo<int>:构造函数和 Display() 使用通用版本,只有 Bar() 使用特例化版本
    • Foo<double>:所有成员都使用通用版本(因为没有特化)

网站公告

今日签到

点亮在社区的每一天
去签到