template关键字

在C++中,template关键字用于定义模板,它是实现泛型编程的核心。

模板基本语法

函数模板

cpp 复制代码
#include <iostream>

// 使用模板
template <typename T>
T my_max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int a = 5, b = 10;
    double x = 3.14, y = 2.71;
    
    // 编译器自动推导 T 的类型
    std::cout << "my_max(a, b) = " << my_max(a, b) << std::endl;      // T = int
    std::cout << "my_max(x, y) = " << my_max(x, y) << std::endl;      // T = double
    
    // 也可以显式指定类型
    std::cout << "my_max<double>(a, b) = " << my_max<double>(a, b) << std::endl;  // T = double
    
    return 0;
}

T是什么?

  • T 是一个模板类型参数,它是一个占位符

  • 在编译时,编译器会根据调用时传入的参数类型自动推导 T 的具体类型

  • T 可以是任何支持 > 运算符的类型(int, double, string 等)

类模板

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>  // 用于std::runtime_error

template <typename T>// T是类型参数,可以是任何数据类型
class Stack {
private:
    std::vector<T> elements;// 使用std::vector作为底层存储容器
    
public:
    // 入栈操作
    void push(const T& value) {
        elements.push_back(value);// 将元素添加到vector末尾
    }
    // 出栈操作
    T pop() {
        if (elements.empty()) {
            throw std::runtime_error("Stack is empty"); // 栈空时抛出异常
        }
        T value = elements.back();// 获取栈顶元素
        elements.pop_back();//移除栈顶元素
        return value;//返回栈顶元素
    }
    // 检查栈是否为空
    bool empty() const {
        return elements.empty();
    }
    
    // 添加更多实用方法
    T top() const {
        if (elements.empty()) {
            throw std::runtime_error("Stack is empty");
        }
        return elements.back();
    }
    
    size_t size() const {
        return elements.size();
    }
};

int main() {
    // 创建整数栈
    Stack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    intStack.push(3);
    
    std::cout << "整数栈大小: " << intStack.size() << std::endl;
    std::cout << "栈顶元素: " << intStack.top() << std::endl;
    
    // 弹出所有元素
    while (!intStack.empty()) {
        std::cout << "弹出: " << intStack.pop() << std::endl;
    }
    
    // 创建字符串栈
    Stack<std::string> stringStack;
    stringStack.push("Hello");
    stringStack.push("World");
    stringStack.push("C++");
    
    std::cout << "\n字符串栈内容:" << std::endl;
    while (!stringStack.empty()) {
        std::cout << stringStack.pop() << std::endl;
    }
    
    return 0;
}
/*
输出结果:

整数栈大小: 3
栈顶元素: 3
弹出: 3
弹出: 2
弹出: 1

字符串栈内容:
C++
World
Hello

*/

模板参数 T 的作用

T 是一个类型占位符,在实例化时会被具体类型替换:

  • Stack → T 变成 int

  • Stackstd::string → T 变成 std::string

  • Stack → T 变成 double

模板参数类型

类型参数(typename/class)

cpp 复制代码
// 定义有两个类型参数的模板类
template <typename T1, class T2>  // typename 和 class 在模板参数中完全等价
class Pair {
private:
    T1 first;   // 第一个数据成员,类型为 T1
    T2 second;  // 第二个数据成员,类型为 T2
    
public:
    // 构造函数:用两个参数初始化 first 和 second
    Pair(T1 f, T2 s) : first(f), second(s) {}
    
    // 获取第一个元素的常量成员函数
    T1 getFirst() const { return first; }
    
    // 获取第二个元素的常量成员函数
    T2 getSecond() const { return second; }
};
// 使用
Pair<int, std::string> person(1, "Alice");
  • T1 和 T2 都是类型参数

  • typename 和 class 在模板参数中完全等价,可以互换使用

  • 每个实例化的 Pair 类都会绑定到特定的 T1 和 T2 类型

完整示例

cpp 复制代码
#include <iostream>
#include <string>

template <typename T1, class T2>
class Pair {
private:
    T1 first;
    T2 second;
    
public:
    Pair(T1 f, T2 s) : first(f), second(s) {}
    
    T1 getFirst() const { return first; }
    T2 getSecond() const { return second; }
    
    // 可以添加设置方法
    void setFirst(T1 f) { first = f; }
    void setSecond(T2 s) { second = s; }
    
    // 可以添加显示方法
    void display() const {
        std::cout << "(" << first << ", " << second << ")" << std::endl;
    }
};

int main() {
    // 示例1:整数和字符串的Pair
    Pair<int, std::string> person(1, "Alice");
    std::cout << "ID: " << person.getFirst() << ", Name: " << person.getSecond() << std::endl;
    
    // 示例2:字符串和浮点数的Pair
    Pair<std::string, double> product("Apple", 2.99);
    std::cout << "Product: " << product.getFirst() << ", Price: $" << product.getSecond() << std::endl;
    
    // 示例3:相同类型的Pair
    Pair<double, double> coordinates(3.14, 2.71);
    std::cout << "Coordinates: (" << coordinates.getFirst() << ", " << coordinates.getSecond() << ")" << std::endl;
    
    // 示例4:使用显示方法
    Pair<int, std::string> student(101, "Bob");
    student.display();
    
    return 0;
}
/*
输出结果:
ID: 1, Name: Alice
Product: Apple, Price: $2.99
Coordinates: (3.14, 2.71)
(101, Bob)
*/

非类型模板参数

这是一个固定大小数组模板类,使用了非类型模板参数来指定数组大小。

cpp 复制代码
// 模板参数:一个类型参数 T 和一个非类型参数 Size
template <typename T, int Size>
class FixedArray {
private:
    T data[Size];  // 固定大小的数组,大小在编译时确定
    
public:
    // 重载下标运算符(非常量版本)
    T& operator[](int index) {
        return data[index];  // 返回元素的引用,允许修改
    }
    
    // 重载下标运算符(常量版本)
    const T& operator[](int index) const {
        return data[index];  // 返回常量引用,不允许修改
    }
    /*
    const 关键字表示这个函数不会修改对象状态
    用于常量对象的访问
    */
    
    // 静态常量成员函数,返回数组大小
    static constexpr int size() { return Size; }
};
// 使用
FixedArray<double, 10> array;  // 创建大小为10的double数组

模板默认参数

cpp 复制代码
template <typename T = int, int Size = 100>
class Container {
    // 实现
};

// 使用
Container<> container1;        // 使用默认参数 T=int, Size=100
Container<double> container2;  // T=double, Size=100
Container<double, 50> container3; // T=double, Size=50

模板特化*****

全特化

cpp 复制代码
#include <iostream>
#include <string>

// 主模板
template <typename T>
class Printer {
public:
    void print(const T& value) {
        std::cout << "Generic: " << value << std::endl;
    }
};

// 全特化 - 为 std::string 提供特殊实现
template <>
class Printer<std::string> {
public:
    void print(const std::string& value) {
        std::cout << "String: \"" << value << "\"" << std::endl;
    }
};

// 可以为更多类型提供特化
template <>
class Printer<double> {
public:
    void print(const double& value) {
        std::cout << "Double: " << std::fixed << value << std::endl;
    }
};

// 为指针类型提供特化
template <typename T>
class Printer<T*> {
public:
    void print(T* value) {
        if (value) {
            std::cout << "Pointer: " << *value << std::endl;
        } else {
            std::cout << "Pointer: nullptr" << std::endl;
        }
    }
};

int main() {
    // 使用主模板
    Printer<int> intPrinter;
    intPrinter.print(42);          // 输出: Generic: 42
    
    // 使用 std::string 特化
    Printer<std::string> strPrinter;
    strPrinter.print("Hello");     // 输出: String: "Hello"
    
    // 使用 double 特化
    Printer<double> doublePrinter;
    doublePrinter.print(3.14159);  // 输出: Double: 3.141590
    
    // 使用指针特化
    int x = 100;
    Printer<int*> ptrPrinter;
    ptrPrinter.print(&x);          // 输出: Pointer: 100
    
    Printer<int*> nullPtrPrinter;
    nullPtrPrinter.print(nullptr); // 输出: Pointer: nullptr
    
    return 0;
}
  • 针对具体类型的特化

  • 完全重写类的实现

  • 可以有不同的成员函数和数据成员

偏特化

cpp 复制代码
// 主模板
template <typename T1, typename T2>
class MyClass {
public:
    void print() {
        std::cout << "Primary template" << std::endl;
    }
};
/*
接受两个任意类型参数
提供通用实现
*/

// 偏特化 - 两个类型相同的情况
template <typename T>
class MyClass<T, T> {
public:
    void print() {
        std::cout << "Both types are the same" << std::endl;
    }
};
/*
当 T1 和 T2 是相同类型时使用
语法:MyClass<T, T>
*/

// 偏特化 - 第二个类型为int的情况
template <typename T>
class MyClass<T, int> {
public:
    void print() {
        std::cout << "Second type is int" << std::endl;
    }
};
/*
当第二个模板参数是 int 时使用
语法:MyClass<T, int>
*/
// 使用
int main() {
    MyClass<int, double> obj1;
    obj1.print();  // Primary template
    
    MyClass<int, int> obj2;
    obj2.print();  // Both types are the same
    
    MyClass<double, int> obj3;
    obj3.print();  // Second type is int
    
    return 0;
}

完整示例

cpp 复制代码
#include <iostream>

// 主模板
template <typename T1, typename T2>
class MyClass {
public:
    void print() {
        std::cout << "Primary template" << std::endl;
    }
};

// 偏特化 - 两个类型相同的情况
template <typename T>
class MyClass<T, T> {
public:
    void print() {
        std::cout << "Both types are the same" << std::endl;
    }
};

// 偏特化 - 第二个类型为int的情况
template <typename T>
class MyClass<T, int> {
public:
    void print() {
        std::cout << "Second type is int" << std::endl;
    }
};

// 偏特化 - 第一个类型为指针的情况
template <typename T1, typename T2>
class MyClass<T1*, T2> {
public:
    void print() {
        std::cout << "First type is a pointer" << std::endl;
    }
};

int main() {
    // 使用主模板
    MyClass<int, double> obj1;
    obj1.print();  // Primary template
    
    // 使用第一个偏特化(类型相同)
    MyClass<int, int> obj2;
    obj2.print();  // Both types are the same
    
    // 使用第二个偏特化(第二个参数为int)
    MyClass<double, int> obj3;
    obj3.print();  // Second type is int
    
    // 使用第三个偏特化(第一个参数为指针)
    MyClass<int*, double> obj4;
    obj4.print();  // First type is a pointer
    
    // 测试优先级:同时匹配多个偏特化时选择最特化的
    MyClass<int*, int> obj5;  // 匹配:指针特化和int特化
    obj5.print();  // 输出:First type is a pointer(指针特化更具体)
    
    return 0;
}

全特化和偏特化的区别

全特化(Full Specialization)

  • 定义:为模板的所有参数提供具体的类型
  • 语法:template<> class ClassName<SpecificTypes...>
cpp 复制代码
// 主模板
template <typename T1, typename T2>
class Example {
public:
    void print() { std::cout << "Primary" << std::endl; }
};

// 全特化 - 所有参数都具体指定
template <>
class Example<int, std::string> {
public:
    void print() { std::cout << "Full specialization" << std::endl; }
};

// 使用
Example<int, std::string> obj;  // 使用全特化版本

特点

  • 所有模板参数都被具体类型替换

  • 是一个完全具体的类,不再是模板

  • 可以完全重新设计实现,不依赖主模板

偏特化(Partial Specialization)

  • 定义:只对部分模板参数提供具体类型或施加约束
  • 语法:template<typename...> class ClassName<PartialTypes...>
cpp 复制代码
// 主模板
template <typename T1, typename T2>
class Example {
public:
    void print() { std::cout << "Primary" << std::endl; }
};

// 偏特化 - 第二个参数固定为int
template <typename T>
class Example<T, int> {
public:
    void print() { std::cout << "T and int" << std::endl; }
};

// 偏特化 - 两个参数类型相同
template <typename T>
class Example<T, T> {
public:
    void print() { std::cout << "Same types" << std::endl; }
};

// 使用
Example<double, int> obj1;      // 使用 T and int 偏特化
Example<float, float> obj2;     // 使用 Same types 偏特化
Example<double, std::string> obj3; // 使用主模板

特点

  • 仍然是模板,有未指定的模板参数

  • 对参数施加模式约束(如类型相同、指针类型等)

  • 必须遵循主模板的基本结构

对比表格


小结

  • 全特化:为特定类型组合提供完全定制的实现

  • 偏特化:为一类类型模式提供特殊实现

  • 选择规则:全特化 > 偏特化 > 主模板

  • 偏特化更灵活:可以定义类型关系(相同、指针、引用等模式)

可变参数模板

基本用法

这是一个使用可变参数模板 (Variadic Templates)和递归展开实现的通用打印函数,可以接受任意数量、任意类型的参数。

cpp 复制代码
// 递归终止函数
void print() {
    std::cout << std::endl;
}
/*
当参数包为空时调用此函数
输出换行符并结束递归
*/

// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用
}
/*
typename... Args:参数包,表示0个或多个类型
Args... args:函数参数包,表示0个或多个参数
递归调用:每次处理一个参数,剩余参数继续递归
*/
// 使用
int main() {
    print(1, 2.5, "hello", 'a');  // 输出: 1 2.5 hello a
    return 0;
}
/*
// 第一次调用
print<int, double, const char*, char>(1, 2.5, "hello", 'a')
→ 输出: "1 "
→ 调用: print(2.5, "hello", 'a')

// 第二次调用  
print<double, const char*, char>(2.5, "hello", 'a')
→ 输出: "2.5 "
→ 调用: print("hello", 'a')

// 第三次调用
print<const char*, char>("hello", 'a')
→ 输出: "hello "
→ 调用: print('a')

// 第四次调用
print<char>('a')
→ 输出: "a "
→ 调用: print()

// 第五次调用
print()
→ 输出: 换行符
→ 递归终止
*/

折叠表达式(C++17)

折叠表达式是 C++17 引入的新特性,它允许对参数包中的所有元素应用二元运算符,无需递归展开。

cpp 复制代码
// C++17 折叠表达式
// 打印函数的折叠表达式版本
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // 一元右折叠
}
/*
展开过程;
// 调用 print(1, 2, 3) 展开为:
std::cout << 1 << 2 << 3 << std::endl;

// 相当于:
((std::cout << 1) << 2) << 3 << std::endl;
*/

//求和函数的折叠表达式版本
template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 一元右折叠
}
/*
 调用 sum(1, 2, 3, 4, 5) 展开为:
1 + (2 + (3 + (4 + 5)));

计算结果:15
*/

// 使用
int main() {
    print(1, 2, 3);           // 输出: 123
    std::cout << sum(1, 2, 3, 4, 5) << std::endl;  // 输出: 15
    return 0;
}

折叠表达式的四种形式

  1. 一元右折叠 (args op ...)
cpp 复制代码
template <typename... Args>
auto right_fold(Args... args) {
    return (args + ...);  // 展开为: arg1 + (arg2 + (arg3 + ...))
}
  1. 一元左折叠 (... op args)
cpp 复制代码
template <typename... Args>
auto left_fold(Args... args) {
    return (... + args);  // 展开为: ((arg1 + arg2) + arg3) + ...
}
  1. 二元右折叠 (args op ... op init)
cpp 复制代码
template <typename... Args>
auto binary_right_fold(Args... args) {
    return (args + ... + 0);  // 展开为: arg1 + (arg2 + (arg3 + 0))
}
  1. 二元左折叠 (init op ... op args)
cpp 复制代码
template <typename... Args>
auto binary_left_fold(Args... args) {
    return (0 + ... + args);  // 展开为: ((0 + arg1) + arg2) + arg3
}

完整示例

cpp 复制代码
#include <iostream>

// C++17 折叠表达式
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // 一元右折叠
}

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 一元右折叠
}

// 更多折叠表达式示例
template <typename... Args>
auto product(Args... args) {
    return (args * ...);  // 一元右折叠:乘法
}

template <typename... Args>
bool all_true(Args... args) {
    return (args && ...);  // 一元右折叠:逻辑与
}

template <typename... Args>
bool any_true(Args... args) {
    return (args || ...);  // 一元右折叠:逻辑或
}

int main() {
    // 测试打印函数
    print(1, 2, 3);                              // 输出: 123
    print("Hello", " ", "World", "!");           // 输出: Hello World!
    print(1, " + ", 2, " = ", 3);                // 输出: 1 + 2 = 3
    
    // 测试求和函数
    std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << std::endl;        // 输出: 15
    std::cout << "Sum: " << sum(1.5, 2.5, 3.5) << std::endl;        // 输出: 7.5
    
    // 测试其他折叠操作
    std::cout << "Product: " << product(2, 3, 4) << std::endl;      // 输出: 24
    std::cout << "All true: " << all_true(true, true, true) << std::endl;    // 输出: 1
    std::cout << "Any true: " << any_true(false, true, false) << std::endl;  // 输出: 1
    
    return 0;
}

模板高级特性

SFINAE(替换失败不是错误)

std::enable_if 工作原理

cpp 复制代码
template<bool Condition, typename T = void>
struct enable_if;

// 特化:当Condition为true时
template<typename T>
struct enable_if<true, T> {
    using type = T;  // 定义type成员
};

// 特化:当Condition为false时  
template<typename T>
struct enable_if<false, T> {
    // 没有type成员!这会导致SFINAE
};

SFINAE(Substitution Failure Is Not An Error)机制

  • 当模板参数推导失败时,编译器不会报错,而是从候选函数中移除该模板

  • 只有满足条件的模板才会被实例化

使用 constexpr if(C++17)

cpp 复制代码
// 方法2:使用 constexpr if (C++17)
template <typename T>
auto process_modern(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral: " << value << std::endl;
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Floating point: " << value << std::endl;
    } else {
        std::cout << "Other type" << std::endl;
    }
    return value;
}

constexpr if 特点

  • 编译期求值:条件在编译时确定

  • 分支消除:只有满足条件的分支会被编译

  • 语法简洁:不需要多个模板重载

完整示例

cpp 复制代码
#include <iostream>
#include <type_traits>
#include <string>

template <typename T>
auto process_modern(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral: " << value;
        if constexpr (sizeof(T) == 1) {
            std::cout << " (char type)";
        } else if constexpr (sizeof(T) == 4) {
            std::cout << " (32-bit int)";
        }
        std::cout << std::endl;
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Floating point: " << value;
        if constexpr (std::is_same_v<T, float>) {
            std::cout << " (float)";
        } else if constexpr (std::is_same_v<T, double>) {
            std::cout << " (double)";
        }
        std::cout << std::endl;
    } else if constexpr (std::is_pointer_v<T>) {
        std::cout << "Pointer to: " << *value << std::endl;
    } else if constexpr (std::is_same_v<T, std::string>) {
        std::cout << "String: \"" << value << "\" (length: " << value.length() << ")" << std::endl;
    } else {
        std::cout << "Other type: " << value << std::endl;
    }
    return value;
}

int main() {
    process_modern(42);                    // Integral
    process_modern(3.14f);                 // Floating point (float)
    process_modern(2.718);                 // Floating point (double)
    
    int y = 200;
    process_modern(&y);                    // Pointer
    
    process_modern(std::string("Hello"));  // String
    process_modern(true);                  // Other type (bool)
    
    return 0;
}

两种方法对比

SFINAE (std::enable_if) 方法
优点

  • 兼容 C++11

  • 明确的函数重载,编译器可以更好地优化

  • 可以用于类模板特化

缺点

  • 语法复杂,可读性差

  • 需要写多个重载版本

  • 错误信息不友好

constexpr if 方法
优点

  • 语法简洁,可读性好

  • 只需要一个函数模板

  • 更好的错误信息

  • 可以嵌套使用

缺点

  • 需要 C++17 支持

  • 所有分支必须在语法上有效(即使不被编译)

模板元编程

cpp 复制代码
// 编译期计算阶乘
template <int N>
struct Factorial {
    static constexpr int value = N * Factorial<N - 1>::value;
};
/*
这是一个递归模板,通过 Factorial<N-1> 递归调用

static constexpr 表示这是一个编译期常量

计算公式:N! = N × (N-1)!
*/

//全特化(终止条件)
template <>
struct Factorial<0> {
    static constexpr int value = 1;
};
/*
为 N=0 提供特化,定义递归终止条件
数学定义:0! = 1
*/
// 使用
int main() {
    std::cout << Factorial<5>::value << std::endl;  // 输出: 120
    return 0;
}
//当计算 Factorial<5>::value 时,编译器展开过程如下:
/*
Factorial<5>::value 
= 5 * Factorial<4>::value
= 5 * (4 * Factorial<3>::value)
= 5 * (4 * (3 * Factorial<2>::value))
= 5 * (4 * (3 * (2 * Factorial<1>::value)))
= 5 * (4 * (3 * (2 * (1 * Factorial<0>::value))))
= 5 * (4 * (3 * (2 * (1 * 1))))  // Factorial<0>::value = 1
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
*/

完整代码

cpp 复制代码
#include <iostream>

// 编译期计算阶乘
template <int N>
struct Factorial {
    static constexpr int value = N * Factorial<N - 1>::value;
};

template <>
struct Factorial<0> {
    static constexpr int value = 1;
};

int main() {
    std::cout << "Factorial<0>::value = " << Factorial<0>::value << std::endl;  // 1
    std::cout << "Factorial<1>::value = " << Factorial<1>::value << std::endl;  // 1
    std::cout << "Factorial<5>::value = " << Factorial<5>::value << std::endl;  // 120
    std::cout << "Factorial<10>::value = " << Factorial<10>::value << std::endl; // 3628800
    
    // 编译期验证
    static_assert(Factorial<0>::value == 1, "0! should be 1");
    static_assert(Factorial<5>::value == 120, "5! should be 120");
    static_assert(Factorial<10>::value == 3628800, "10! should be 3628800");
    
    // 可以在编译期常量需要的地方使用
    int array[Factorial<4>::value];  // 创建大小为24的数组
    std::cout << "数组大小: " << sizeof(array)/sizeof(array[0]) << std::endl;
    
    return 0;
}

更多示例

  1. 斐波那契数列
cpp 复制代码
// 编译期计算斐波那契数列
template <int N>
struct Fibonacci {
    static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};

template <>
struct Fibonacci<0> {
    static constexpr int value = 0;
};

template <>
struct Fibonacci<1> {
    static constexpr int value = 1;
};

int main() {
    std::cout << "Fibonacci<0> = " << Fibonacci<0>::value << std::endl;  // 0
    std::cout << "Fibonacci<1> = " << Fibonacci<1>::value << std::endl;  // 1
    std::cout << "Fibonacci<10> = " << Fibonacci<10>::value << std::endl; // 55
    
    return 0;
}
  1. 最大公约数(GCD)
cpp 复制代码
// 编译期计算最大公约数
template <int A, int B>
struct GCD {
    static constexpr int value = GCD<B, A % B>::value;
};

template <int A>
struct GCD<A, 0> {
    static constexpr int value = A;
};

int main() {
    std::cout << "GCD(48, 18) = " << GCD<48, 18>::value << std::endl;  // 6
    std::cout << "GCD(1071, 462) = " << GCD<1071, 462>::value << std::endl; // 21
    
    return 0;
}

概念(C++20)

cpp 复制代码
// C++20 概念
template <typename T>
concept Integral = std::is_integral_v<T>;
/*
使用类型特征 std::is_integral_v 来定义概念

满足条件的类型:int, long, short, char, bool 等

不满足的类型:float, double, std::string 等
*/

template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};
/*
使用 requires 表达式定义概念

检查类型 T 是否支持 + 运算符

要求 a + b 的返回类型必须与 T 相同
*/

// 使用概念约束模板
template <Integral T>
T add_integral(T a, T b) {
    return a + b;
}

template <Addable T>
T add_any(T a, T b) {
    return a + b;
}

// 使用
int main() {
    add_integral(5, 3);      // OK
    // add_integral(3.14, 2.71);  // 编译错误:不满足 Integral 概念
    
    add_any(5, 3);           // OK
    add_any(std::string("hello"), std::string("world"));  // OK
    
    return 0;
}

完整代码

cpp 复制代码
#include <iostream>
#include <type_traits>
#include <string>
#include <concepts>  // C++20 概念头文件

// 方法1:使用类型特征定义概念
template <typename T>
concept Integral = std::is_integral_v<T>;

template <typename T>
concept FloatingPoint = std::is_floating_point_v<T>;

// 方法2:使用 requires 表达式定义概念
template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};

template <typename T>
concept Subtractable = requires(T a, T b) {
    { a - b } -> std::same_as<T>;
};

template <typename T>
concept Printable = requires(T a) {
    { std::cout << a } -> std::same_as<std::ostream&>;
};

// 方法3:使用标准库预定义概念
template <typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;

// 使用概念约束模板函数
template <Integral T>
T add_integral(T a, T b) {
    std::cout << "Adding integers: " << a << " + " << b;
    T result = a + b;
    std::cout << " = " << result << std::endl;
    return result;
}

template <Addable T>
T add_any(T a, T b) {
    std::cout << "Adding addable types: " << a << " + " << b;
    T result = a + b;
    std::cout << " = " << result << std::endl;
    return result;
}

// 多个概念组合
template <typename T>
requires Addable<T> && Subtractable<T>
T calculate(T a, T b) {
    return (a + b) * (a - b);
}

// 概念用于自动类型推导
Printable auto print_value(Printable auto value) {
    std::cout << "Value: " << value << std::endl;
    return value;
}

int main() {
    // 测试 Integral 概念
    add_integral(5, 3);          // OK
    // add_integral(3.14, 2.71); // 编译错误:不满足 Integral 概念
    
    // 测试 Addable 概念
    add_any(5, 3);               // OK
    add_any(std::string("hello"), std::string("world"));  // OK
    
    // 测试多个概念组合
    std::cout << "Calculate: " << calculate(10, 3) << std::endl;  // (10+3)*(10-3) = 91
    
    // 测试自动类型推导
    print_value(42);             // OK
    print_value("Hello");        // OK
    // print_value(std::vector<int>{}); // 编译错误:不满足 Printable 概念
    
    return 0;
}

模板实战示例

工厂模式模板

这是一个使用C++模板实现的通用工厂模式(Generic Factory Pattern),可以动态创建继承自同一基类的不同派生类对象。

cpp 复制代码
template <typename Base>
class Factory {
private:
    using Creator = std::function<std::unique_ptr<Base>()>;
/*
定义了一个函数类型,该函数无参数,返回 std::unique_ptr<Base>

每个注册的类都会对应一个这样的创建函数
*/
    std::unordered_map<std::string, Creator> creators;
    /*
    使用字符串作为键,创建函数作为值
    例如:"Circle" → 创建 Circle 对象的函数
   */
public:
    template <typename Derived>
    void registerClass(const std::string& name) {
        creators[name] = []() -> std::unique_ptr<Base> {
            return std::make_unique<Derived>();
        };
    }

    /*
   使用 lambda 表达式捕获 Derived 类型信息

   当调用时,创建 Derived 对象并向上转型为 Base 指针
   */
    
    std::unique_ptr<Base> create(const std::string& name) {
        auto it = creators.find(name);
        if (it != creators.end()) {
            return it->second();
        }
        return nullptr;
    }
};

完整代码

cpp 复制代码
#include <iostream>
#include <memory>
#include <unordered_map>
#include <functional>
#include <string>

template <typename Base>
class Factory {
private:
    using Creator = std::function<std::unique_ptr<Base>()>;
    std::unordered_map<std::string, Creator> creators;
    
public:
    template <typename Derived>
    void registerClass(const std::string& name) {
        creators[name] = []() -> std::unique_ptr<Base> {
            return std::make_unique<Derived>();
        };
    }
    
    std::unique_ptr<Base> create(const std::string& name) {
        auto it = creators.find(name);
        if (it != creators.end()) {
            return it->second();
        }
        return nullptr;
    }
    
    // 添加更多实用方法
    bool isRegistered(const std::string& name) const {
        return creators.find(name) != creators.end();
    }
    
    std::vector<std::string> getRegisteredNames() const {
        std::vector<std::string> names;
        for (const auto& pair : creators) {
            names.push_back(pair.first);
        }
        return names;
    }
};

// 基类和派生类定义
class Shape {
public:
    virtual void draw() = 0;
    virtual std::string getName() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() override { 
        std::cout << "Drawing Circle ○" << std::endl; 
    }
    std::string getName() const override { return "Circle"; }
};

class Square : public Shape {
public:
    void draw() override { 
        std::cout << "Drawing Square □" << std::endl; 
    }
    std::string getName() const override { return "Square"; }
};

class Triangle : public Shape {
public:
    void draw() override { 
        std::cout << "Drawing Triangle △" << std::endl; 
    }
    std::string getName() const override { return "Triangle"; }
};

int main() {
    // 创建形状工厂
    Factory<Shape> shapeFactory;
    
    // 注册形状类
    shapeFactory.registerClass<Circle>("Circle");
    shapeFactory.registerClass<Square>("Square");
    shapeFactory.registerClass<Triangle>("Triangle");
    
    // 显示已注册的类型
    std::cout << "Registered shapes: ";
    for (const auto& name : shapeFactory.getRegisteredNames()) {
        std::cout << name << " ";
    }
    std::cout << std::endl;
    
    // 动态创建对象
    auto circle = shapeFactory.create("Circle");
    auto square = shapeFactory.create("Square");
    auto triangle = shapeFactory.create("Triangle");
    auto unknown = shapeFactory.create("Hexagon");  // 未注册的类型
    
    // 使用创建的对象
    if (circle) {
        circle->draw();
        std::cout << "Created: " << circle->getName() << std::endl;
    }
    
    if (square) {
        square->draw();
        std::cout << "Created: " << square->getName() << std::endl;
    }
    
    if (triangle) {
        triangle->draw();
        std::cout << "Created: " << triangle->getName() << std::endl;
    }
    
    if (!unknown) {
        std::cout << "Failed to create unknown shape: Hexagon" << std::endl;
    }
    
    return 0;
}

工厂模式的工作原理

  • 注册阶段:将类名与创建函数关联
cpp 复制代码
factory.registerClass<Circle>("Circle");
// 相当于:creators["Circle"] = 创建Circle对象的函数
  • 创建阶段:根据名称查找并调用对应的创建函数
cpp 复制代码
auto shape = factory.create("Circle");
// 查找 "Circle" → 调用对应的lambda → 返回 new Circle()

策略模式模板

cpp 复制代码
// 排序策略模板
template <typename T, typename Compare = std::less<T>>
/*
1. 模板参数
typename T:要排序的元素类型

typename Compare = std::less<T>:比较策略,默认是升序
*/

class Sorter {
private:
    Compare comp;// 比较策略对象
    /*
    存储比较策略的实例

   可以是函数对象、函数指针或lambda表达式
   */
public:
    void sort(std::vector<T>& vec) {
        std::sort(vec.begin(), vec.end(), comp);// 使用策略对象进行排序
    }
};

完整代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

// 排序策略模板
template <typename T, typename Compare = std::less<T>>
class Sorter {
private:
    Compare comp;
    
public:
    void sort(std::vector<T>& vec) {
        std::sort(vec.begin(), vec.end(), comp);
    }
    
    // 添加更多实用方法
    void sort_range(std::vector<T>& vec, size_t start, size_t end) {
        if (start < end && end <= vec.size()) {
            std::sort(vec.begin() + start, vec.begin() + end, comp);
        }
    }
    
    bool is_sorted(const std::vector<T>& vec) const {
        return std::is_sorted(vec.begin(), vec.end(), comp);
    }
};

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // 升序排序
    Sorter<int, std::less<int>> ascendingSorter;
    ascendingSorter.sort(numbers);
    std::cout << "升序排序: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    // 降序排序
    Sorter<int, std::greater<int>> descendingSorter;
    descendingSorter.sort(numbers);
    std::cout << "降序排序: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    return 0;
}

使用Lambda表达式作为策略

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<std::pair<int, std::string>> pairs = {
        {3, "three"}, {1, "one"}, {4, "four"}, {1, "one-again"}, {2, "two"}
    };
    
    // 使用lambda表达式作为比较策略
    auto pairCompare = [](const auto& a, const auto& b) {
        // 先按数字排序,数字相同按字符串排序
        if (a.first != b.first) {
            return a.first < b.first;
        }
        return a.second < b.second;
    };
    
    // C++17 CTAD (Class Template Argument Deduction)
    Sorter sorter{pairCompare};
    sorter.sort(pairs);
    
    std::cout << "排序后的键值对:" << std::endl;
    for (const auto& p : pairs) {
        std::cout << "{" << p.first << ", \"" << p.second << "\"}" << std::endl;
    }
    
    return 0;
}

策略模式的优势

  • 灵活性:可以在运行时或编译时切换排序策略

  • 可扩展性:轻松添加新的排序策略

  • 复用性:同一套排序逻辑支持不同比较规则

  • 类型安全:编译时检查比较策略的兼容性

  • 性能:无虚函数开销,编译器可以内联优化

模板最佳实践

  • 将模板实现放在头文件中:模板需要在编译时实例化

  • 使用概念约束模板参数(C++20):提高代码可读性和错误信息

  • 避免过度使用模板:模板会增加编译时间和代码体积

  • 使用SFINAE或概念进行条件编译:提供更好的类型安全

  • 考虑模板特化:为特定类型提供优化实现

模板是C++最强大的特性之一,合理使用可以编写出高效、类型安全且可重用的代码。

相关推荐
mit6.8242 小时前
[Avoid-MPC] AvoidanceStateMachine | `Step`心跳函数 | Callback设计
c++
ceclar1232 小时前
C++文件操作
开发语言·c++
CSDN_RTKLIB2 小时前
【动态链接库】一、VS下基本制作与使用
c++
高一要励志成为佬2 小时前
【C++】vector的迭代器失效问题,(什么是迭代器失效,为什么会产生迭代器失效,怎么避免迭代器失效问题)
开发语言·c++
CaracalTiger2 小时前
本地部署 Stable Diffusion3.5!cpolar让远程访问很简单!
java·linux·运维·开发语言·python·微信·stable diffusion
whm27772 小时前
Visual Basic 创建状态栏
开发语言·visual studio
落笔映浮华丶3 小时前
蓝桥杯零基础到获奖-第4章 C++ 变量和常量
java·c++·蓝桥杯
培林将军3 小时前
Visual Studio Code 之C/C++开发编译环境搭建
c语言·c++·vscode
api_180079054603 小时前
【技术教程】Python/Node.js 调用拼多多商品详情 API 示例详解
大数据·开发语言·python·数据挖掘·node.js