【C++】模板与特化技术全面教程(claude sonnet 4)
第一章:模板的基础概念 (Template Fundamentals)
1.1 什么是模板?
模板 (Template) 是C++中的一种泛型编程 (Generic Programming) 机制,它允许我们编写与类型无关的代码。想象一下,如果我们要为不同的数据类型编写相同逻辑的函数,没有模板的话,我们需要为每种类型都写一遍代码。
模板的核心思想是代码复用 (Code Reuse) 和类型安全 (Type Safety)。编译器会根据实际使用的类型自动生成相应的代码,这个过程称为模板实例化 (Template Instantiation)。
1.2 模板的基本语法
// 基本模板语法结构template<typename T> // 或者使用 template<class T>返回类型 函数名(参数列表) { // 函数体}
注意:typename
和 class
在这里是等价的,但现代C++推荐使用 typename
,因为它更清晰地表达了T是一个类型参数。
第二章:函数模板 (Function Templates)
2.1 简单函数模板
让我们从一个简单的例子开始理解函数模板:
#include <iostream>
// 找出两个值中的最大值的模板函数template<typename T>T max_value(T a, T b) { return (a > b) ? a : b;}
int main() { // 编译器会自动推导类型 (Type Deduction) std::cout << max_value(10, 20) << std::endl; // T = int std::cout << max_value(3.14, 2.71) << std::endl; // T = double std::cout << max_value('a', 'z') << std::endl; // T = char// 也可以显式指定类型 (Explicit Type Specification) std::cout << max_value<int>(10.5, 20.7) << std::endl; // 强制转换为intreturn 0;}
2.2 多参数模板
模板可以有多个类型参数:
// 多类型参数的模板函数template<typename T1, typename T2>auto add_different_types(T1 a, T2 b) -> decltype(a + b) { return a + b;}
// C++14及以后可以简化为:template<typename T1, typename T2>auto add_different_types_v2(T1 a, T2 b) { return a + b; // 编译器自动推导返回类型}
2.3 非类型模板参数 (Non-type Template Parameters)
除了类型参数,模板还可以接受非类型参数:
#include <array>// 创建固定大小数组并初始化的模板函数template<typename T, std::size_t N>std::array<T, N> create_array(const T& initial_value) { std::array<T, N> arr; arr.fill(initial_value); return arr;}int main() { auto int_array = create_array<int, 5>(42); // 创建5个元素的int数组,都初始化为42 auto double_array = create_array<double, 3>(3.14); // 创建3个元素的double数组 return 0;}
第三章:类模板 (Class Templates)
3.1 基本类模板
类模板允许我们创建泛型类。让我们创建一个简单的栈 (Stack) 类:
#include <vector>#include <stdexcept>
template<typename T>class Stack {private: std::vector<T> elements; // 使用vector存储元素
public: // 入栈操作 void push(const T& element) { elements.push_back(element); }// 出栈操作 T pop() { if (elements.empty()) { throw std::runtime_error("Stack is empty"); } T result = elements.back(); elements.pop_back(); return result; }// 查看栈顶元素 const T& top() const { if (elements.empty()) { throw std::runtime_error("Stack is empty"); } return elements.back(); }// 检查栈是否为空 bool empty() const { return elements.empty(); }// 获取栈的大小 std::size_t size() const { return elements.size(); }};
// 使用示例int main() { Stack<int> int_stack; // 整数栈 Stack<std::string> str_stack; // 字符串栈int_stack.push(10); int_stack.push(20);str_stack.push("Hello"); str_stack.push("World");std::cout << int_stack.pop() << std::endl; // 输出: 20 std::cout << str_stack.pop() << std::endl; // 输出: Worldreturn 0;}
3.2 模板类的成员函数定义
有时我们需要将类模板的成员函数定义在类外部:
template<typename T>class Matrix {private: std::vector<std::vector<T>> data; std::size_t rows, cols;
public: Matrix(std::size_t r, std::size_t c); // 构造函数声明 T& at(std::size_t row, std::size_t col); // 元素访问函数声明 void print() const; // 打印函数声明};
// 构造函数定义(注意模板语法)template<typename T>Matrix<T>::Matrix(std::size_t r, std::size_t c) : rows(r), cols(c), data(r, std::vector<T>(c)) { // 初始化矩阵}
// 成员函数定义template<typename T>T& Matrix<T>::at(std::size_t row, std::size_t col) { if (row >= rows || col >= cols) { throw std::out_of_range("Matrix index out of range"); } return data[row][col];}
template<typename T>void Matrix<T>::print() const { for (const auto& row : data) { for (const auto& element : row) { std::cout << element << " "; } std::cout << std::endl; }}
第四章:模板特化 (Template Specialization)
4.1 全特化 (Full Specialization)
有时候,我们需要为特定的类型提供特殊的实现。这就是模板特化的用处:
#include <iostream>#include <string>
// 通用模板函数template<typename T>void process_data(const T& data) { std::cout << "处理通用数据: " << data << std::endl;}
// 为字符串类型提供特化版本template<>void process_data<std::string>(const std::string& data) { std::cout << "处理字符串数据 (特化版本): \"" << data << "\" (长度: " << data.length() << ")" << std::endl;}
// 为指针类型提供特化版本template<>void process_data<int*>(int* const& data) { if (data) { std::cout << "处理指针数据 (特化版本): 地址=" << data << ", 值=" << *data << std::endl; } else { std::cout << "处理空指针" << std::endl; }}
int main() { process_data(42); // 使用通用版本 process_data(3.14); // 使用通用版本 process_data(std::string("Hello World")); // 使用字符串特化版本int value = 100; process_data(&value); // 使用指针特化版本return 0;}
4.2 类模板的全特化
类模板也可以进行全特化:
#include <iostream>#include <cstring>// 通用的存储类模板template<typename T>class Storage {private: T data;public: Storage(const T& value) : data(value) {} void print() const { std::cout << "存储的数据: " << data << std::endl; } T get() const { return data; }};// 为const char*提供特化版本(处理C风格字符串)template<>class Storage<const char*> {private: char* data;public: Storage(const char* str) { data = new char[std::strlen(str) + 1]; std::strcpy(data, str); } ~Storage() { delete[] data; } // 复制构造函数 Storage(const Storage& other) { data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); } // 赋值操作符 Storage& operator=(const Storage& other) { if (this != &other) { delete[] data; data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); } return *this; } void print() const { std::cout << "存储的C字符串 (特化版本): \"" << data << "\"" << std::endl; } const char* get() const { return data; }};int main() { Storage<int> int_storage(42); Storage<const char*> str_storage("Hello World"); int_storage.print(); // 使用通用版本 str_storage.print(); // 使用特化版本 return 0;}
4.3 偏特化 (Partial Specialization)
偏特化只能用于类模板,不能用于函数模板。它允许我们为模板参数的某些组合提供特殊实现:
#include <iostream> // 包含标准输入输出库,用于后续的输入和输出操作
// 通用的配对类模板// 定义一个模板类 Pair,它能够存储一个配对数据,支持两种不同类型 T1 和 T2template<typename T1, typename T2>class Pair {private: T1 first; // 定义一个变量 first,类型为 T1,用来存储配对中的第一个元素 T2 second; // 定义一个变量 second,类型为 T2,用来存储配对中的第二个元素public: // 构造函数,使用常量引用传参,并利用初始化列表初始化成员变量 Pair(const T1& f, const T2& s) : first(f), second(s) {}// 成员函数 print,用于打印配对的内容(不修改成员变量,故为 const 成员函数) void print() const { // 输出格式为:通用配对: (first, second) std::cout << "通用配对: (" << first << ", " << second << ")" << std::endl; }};
// 偏特化:当两个类型相同时// 下面定义 Pair 的偏特化版本,当两个模板参数类型相同时(即 T1 和 T2 都是 T)使用此版本template<typename T>class Pair<T, T> {private: T first; // 定义变量 first,类型为 T T second; // 定义变量 second,类型为 Tpublic: // 构造函数,同样使用初始化列表初始化两个成员变量 Pair(const T& f, const T& s) : first(f), second(s) {}// 重写 print 函数,以显示偏特化的信息 void print() const { // 输出格式为:相同类型配对 (偏特化): (first, second) std::cout << "相同类型配对 (偏特化): (" << first << ", " << second << ")" << std::endl; }// 额外功能:检查两个值是否相等 // 定义一个成员函数 are_equal(),用于比较 first 和 second 是否相等,并返回布尔值 bool are_equal() const { return first == second; }};
// 偏特化:当第二个类型是指针时// 定义 Pair 的另一个偏特化版本,当第二个模板参数为指针类型 T2* 时使用此版本template<typename T1, typename T2>class Pair<T1, T2*> {private: T1 first; // 定义变量 first,类型为 T1 T2* second; // 定义变量 second,类型为 T2*,即指针类型public: // 构造函数,接受 T1 类型的值和 T2 指针,通过初始化列表初始化成员变量 Pair(const T1& f, T2* s) : first(f), second(s) {}// 重写 print 函数,专门处理指针类型的输出 void print() const { // 输出起始部分:显示 "指针配对 (偏特化): (first, " std::cout << "指针配对 (偏特化): (" << first << ", "; // 判断指针 second 是否为空 if (second) { // 如果 second 非空,输出指针所指向的数据(解引用) std::cout << *second; } else { // 如果 second 为空,输出 "nullptr" std::cout << "nullptr"; } // 输出闭合括号并换行 std::cout << ")" << std::endl; }};
int main() { // 创建一个 Pair 对象 p1,模板参数为 <int, double>,调用通用版本构造函数 Pair<int, double> p1(1, 2.5); // 使用通用版本:当类型不同 // 创建一个 Pair 对象 p2,模板参数为 <int, int>,因此调用相同类型的偏特化版本 Pair<int, int> p2(3, 4); // 使用偏特化版本(相同类型)int value = 100; // 定义一个 int 类型的变量 value,用于测试指针配对// 创建一个 Pair 对象 p3,模板参数为 <std::string, int*>,调用第二个偏特化版(指针类型) Pair<std::string, int*> p3("数值", &value); // 使用偏特化版本(指针类型)// 调用 p1 的 print 函数打印通用配对数据 p1.print();// 调用 p2 的 print 函数打印相同类型偏特化配对数据 p2.print();// 调用 p2 的 are_equal 函数检查 p2 中两个值是否相等,并将结果打印出来 std::cout << "p2中的值相等吗? " << (p2.are_equal() ? "是" : "否") << std::endl;// 调用 p3 的 print 函数打印指针配对数据,并正确显示指针指向的值 p3.print();return 0; // 返回 0 表示程序正常结束}
第五章:高级模板技术
5.1 SFINAE (Substitution Failure Is Not An Error)
SFINAE是C++模板元编程中的一个重要概念。当模板参数替换失败时,编译器不会报错,而是简单地从重载候选中移除该模板:
#include <iostream>#include <type_traits>// 使用SFINAE检测类型是否有特定成员函数template<typename T>class has_size_method {private: // 这个技巧用于检测T是否有size()方法 template<typename U> static auto test(int) -> decltype(std::declval<U>().size(), std::true_type{}); template<typename> static std::false_type test(...);public: static constexpr bool value = decltype(test<T>(0))::value;};// 根据是否有size方法选择不同的实现template<typename T>typename std::enable_if<has_size_method<T>::value, std::size_t>::typeget_container_info(const T& container) { std::cout << "容器有size方法,大小为: "; return container.size();}template<typename T>typename std::enable_if<!has_size_method<T>::value, std::size_t>::typeget_container_info(const T& container) { std::cout << "容器没有size方法,无法获取大小。返回: "; return 0;}int main() { std::vector<int> vec{1, 2, 3, 4, 5}; int arr[] = {1, 2, 3}; std::cout << get_container_info(vec) << std::endl; // vector有size方法 std::cout << get_container_info(arr) << std::endl; // 数组没有size方法 return 0;}
5.2 变参模板 (Variadic Templates)
C++11引入了变参模板,允许模板接受可变数量的参数:
#include <iostream> // 包含标准输入输出流库,用于后续的输入输出操作
// 递归版本的变参模板函数// 定义处理只有一个参数的模板函数,当只剩下最后一个参数时调用该函数template<typename T>void print_values(const T& value) { // 递归的基础情况:输出传入的单个参数并换行 std::cout << value << std::endl;}
// 定义处理多个参数的模板函数,利用变参模板实现递归调用template<typename T, typename... Args>void print_values(const T& first, const Args&... rest) { // 输出第一个参数并在后面接一个空格 std::cout << first << " "; // 递归调用print_values,将剩余参数传入 print_values(rest...); // 递归调用,逐个处理剩余参数}
// C++17折叠表达式版本 (Fold Expression)// 定义使用折叠表达式实现的变参函数,一次性展开所有参数进行打印template<typename... Args>void print_values_cpp17(const Args&... args) { // 利用折叠表达式对每个参数执行 std::cout << 参数 << " " 的操作 ((std::cout << args << " "), ...); // 所有参数打印完成后输出换行符 std::cout << std::endl;}
// 计算多个值的和// 定义一个使用折叠表达式计算传入所有参数之和的模板函数template<typename... Args>auto sum(Args... args) { // 利用折叠表达式 (args + ...) 计算所有参数累加的结果 return (args + ...); // C++17折叠表达式,依次累加所有参数}
// 变参模板类:元组的简单实现// 声明模板类SimpleTuple,可以接收任意数量和类型的参数,用于保存一组数据template<typename... Types>class SimpleTuple;
// 特化:空元组// 当没有传入任何类型参数时,提供一个空特化版本,作为递归终止条件template<>class SimpleTuple<> {};
// 递归定义:头部+尾部// 当有至少一个参数时,将第一个元素作为头部,其余元素构成尾部元组template<typename Head, typename... Tail>class SimpleTuple<Head, Tail...> {private: Head head; // 保存元组的第一个元素,即头部 SimpleTuple<Tail...> tail; // 保存剩余的元素(尾部),以一个SimpleTuple表示public: // 构造函数,使用初始化列表依次初始化头部元素和尾部元组 SimpleTuple(Head h, Tail... t) : head(h), tail(t...) {}// 返回头部元素的引用,可供外部访问和修改 Head& get_head() { return head; } // 常量版本,返回头部元素的常量引用,保护数据不被修改 const Head& get_head() const { return head; }// 返回尾部(子元组)的引用,用于递归访问余下元素 SimpleTuple<Tail...>& get_tail() { return tail; } // 常量版本,返回尾部(子元组)的常量引用 const SimpleTuple<Tail...>& get_tail() const { return tail; }};
int main() { // 测试变参函数:使用递归版本打印多个参数 print_values(1, 2.5, "Hello", 'A');// 测试变参函数:使用 C++17 折叠表达式版本打印多个参数 print_values_cpp17(1, 2.5, "World", 'B');// 测试求和函数:依次累加整数和浮点数 std::cout << "1+2+3+4+5 = " << sum(1, 2, 3, 4, 5) << std::endl; std::cout << "1.5+2.7+3.2 = " << sum(1.5, 2.7, 3.2) << std::endl;// 测试简单元组的实现 // 创建一个SimpleTuple对象,包含 int、double 和 std::string 三种类型的元素 SimpleTuple<int, double, std::string> tuple(42, 3.14, "Hello"); // 访问并打印元组中的头部元素 std::cout << "元组第一个元素: " << tuple.get_head() << std::endl; return 0; // 返回0表示程序正常结束}
第六章:模板元编程 (Template Metaprogramming)
6.1 编译期计算
模板可以在编译期进行计算,这是模板元编程的基础:
#include <iostream> // 包含标准输入输出流库,用于后续在终端输出结果
// 编译期计算阶乘// 定义模板结构体 Factorial,通过模板递归在编译期计算阶乘的值template<int N>struct Factorial { // 定义静态 constexpr 成员 value,其值为 N 乘以 Factorial<N - 1> 的值 static constexpr int value = N * Factorial<N - 1>::value;};
// 特化:递归终止条件// 当 N == 0 时,阶乘定义为1,此特化用于递归终止,防止无限递归template<>struct Factorial<0> { // 定义静态 constexpr 成员 value,其值为 1 static constexpr int value = 1;};
// 编译期计算斐波那契数列// 定义模板结构体 Fibonacci,通过模板递归在编译期计算斐波那契数template<int N>struct Fibonacci { // 定义静态 constexpr 成员 value,其值为 Fibonacci<N - 1> 与 Fibonacci<N - 2> 的和 static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;};
// 特化:递归终止条件(第一种情况)// 当 N == 0 时,斐波那契数值定义为 0,此特化用于递归终止template<>struct Fibonacci<0> { // 定义静态 constexpr 成员 value,其值为 0 static constexpr int value = 0;};
// 特化:递归终止条件(第二种情况)// 当 N == 1 时,斐波那契数值定义为 1,此特化用于递归终止template<>struct Fibonacci<1> { // 定义静态 constexpr 成员 value,其值为 1 static constexpr int value = 1;};
// 使用constexpr函数 (C++11及以后的现代方法)// 定义 constexpr 函数 factorial_func,通过递归在编译期或运行期计算阶乘constexpr int factorial_func(int n) { // 如果 n 小于等于1,则返回1;否则返回 n 乘以 factorial_func(n - 1) return (n <= 1) ? 1 : n * factorial_func(n - 1);}
// 定义 constexpr 函数 fibonacci_func,通过递归在编译期或运行期计算斐波那契数列的值constexpr int fibonacci_func(int n) { // 如果 n 小于等于1,则直接返回 n;否则返回 fibonacci_func(n - 1) 与 fibonacci_func(n - 2) 的和 return (n <= 1) ? n : fibonacci_func(n - 1) + fibonacci_func(n - 2);}
int main() { // 这些值在编译期就计算好了 // 通过模板方式计算5的阶乘,并将结果赋予 fact5,其值应为120 constexpr int fact5 = Factorial<5>::value; // 120 // 通过模板方式计算第10个斐波那契数,并将结果赋予 fib10,其值应为55 constexpr int fib10 = Fibonacci<10>::value; // 55 // 使用 constexpr 函数计算5的阶乘,赋值给 fact5_func,结果同样为120 constexpr int fact5_func = factorial_func(5); // 120 // 使用 constexpr 函数计算第10个斐波那契数,赋值给 fib10_func,结果同样为55 constexpr int fib10_func = fibonacci_func(10); // 55// 输出模板版本和 constexpr函数版本计算的结果 std::cout << "5! = " << fact5 << " (模板版本)" << std::endl; std::cout << "5! = " << fact5_func << " (constexpr函数版本)" << std::endl; std::cout << "fibonacci(10) = " << fib10 << " (模板版本)" << std::endl; std::cout << "fibonacci(10) = " << fib10_func << " (constexpr函数版本)" << std::endl;return 0; // 返回0表示程序正常结束}
6.2 类型萃取 (Type Traits)
类型萃取用于在编译期获取类型的信息:
#include <iostream> // 包含输入输出流库,用于输出信息#include <type_traits> // 包含标准类型萃取工具,比如 std::is_integral 等
// 自定义的类型萃取:检查是否是指针类型// 默认情况下,所有类型都不是指针,所以 value 为 falsetemplate<typename T>struct is_pointer { static constexpr bool value = false; // 非指针类型时,值为 false};
// 当类型为指针时(T*),特化版本将 value 设置为 truetemplate<typename T>struct is_pointer<T*> { static constexpr bool value = true; // 指针类型时,值为 true};
// 移除指针的类型萃取:如果类型是指针,则返回指针指向的类型;否则返回原类型// 默认情况,不是指针的类型,移除指针后仍为 Ttemplate<typename T>struct remove_pointer { using type = T;};
// 特化版本:对于指针类型 T*,移除指针后得到的类型为 Ttemplate<typename T>struct remove_pointer<T*> { using type = T;};
// 辅助模板别名 (C++14):简化 remove_pointer<T>::type 的写法template<typename T>using remove_pointer_t = typename remove_pointer<T>::type;
// 条件类型选择:根据布尔条件选择类型// 当 Condition 为 true 时,type 定义为 TrueTypetemplate<bool Condition, typename TrueType, typename FalseType>struct conditional { using type = TrueType;};
// 当 Condition 为 false 时,特化版本定义 type 为 FalseTypetemplate<typename TrueType, typename FalseType>struct conditional<false, TrueType, FalseType> { using type = FalseType;};
// 辅助模板别名 (C++14):简化 conditional 的写法template<bool Condition, typename TrueType, typename FalseType>using conditional_t = typename conditional<Condition, TrueType, FalseType>::type;
// 使用类型萃取的函数模板// 此函数分析传入类型 T 的特性,并输出相关信息template<typename T>void analyze_type(const T& value) { std::cout << "分析类型信息:" << std::endl; // 判断类型 T 是否为指针类型,输出结果 std::cout << " 是否是指针: " << (is_pointer<T>::value ? "是" : "否") << std::endl; // 利用标准类型萃取判断是否为整数类型 std::cout << " 是否是整数: " << (std::is_integral<T>::value ? "是" : "否") << std::endl; // 利用标准类型萃取判断是否为浮点数类型 std::cout << " 是否是浮点数: " << (std::is_floating_point<T>::value ? "是" : "否") << std::endl; // 输出类型 T 的大小(字节数) std::cout << " 类型大小: " << sizeof(T) << " 字节" << std::endl; // 如果 T 是指针类型,则输出指针指向的类型大小 if constexpr (is_pointer<T>::value) { std::cout << " 指向的类型大小: " << sizeof(remove_pointer_t<T>) << " 字节" << std::endl; } // 换行分割 std::cout << std::endl;}
int main() { int x = 42; // 定义一个 int 类型变量 x,并赋值 42 int* px = &x; // 定义一个 int* 类型指针 px,指向变量 x double y = 3.14; // 定义一个 double 类型变量 y,并赋值 3.14analyze_type(x); // 分析 int 类型的信息 analyze_type(px); // 分析 int* 指针类型的信息 analyze_type(y); // 分析 double 类型的信息// 使用条件类型选择:当 sizeof(int) >= 4 为真时,选择 int 类型;否则选择 double 类型 using int_or_double = conditional_t<sizeof(int) >= 4, int, double>; std::cout << "条件选择的类型大小: " << sizeof(int_or_double) << " 字节" << std::endl;return 0; // 程序正常结束,返回 0}
第七章:现代C++模板特性
7.1 概念 (Concepts) - C++20
概念允许我们对模板参数施加约束,使模板更易于使用和理解:
// 注意:这需要C++20支持// 这行注释说明本代码需要使用C++20标准,因为接下来将使用 concepts 相关的语言特性
#include <iostream> // 引入输入输出流库,用于输出信息#include <concepts> // 引入 C++20 的 concepts 库,用于定义概念
// 定义一个概念:可打印的类型template<typename T>concept Printable = requires(T a) { std::cout << a; // 要求类型 T 能够被输出到 std::cout,必须支持 << 运算符};
// 定义一个概念:有 size 方法的类型template<typename T>concept HasSize = requires(T a) { a.size(); // 要求类型 T 必须有一个 size() 方法 { a.size() } -> std::convertible_to<std::size_t>; // 并且 size() 方法的返回值必须可以转换为 std::size_t 类型};
// 使用概念约束的函数模板:只对 Printable 类型有效template<Printable T>void safe_print(const T& value) { // 当传入的类型符合 Printable 概念时,可以安全地打印该值 std::cout << "安全打印: " << value << std::endl;}
// 使用概念约束的函数模板:只对具有 size() 方法的容器类型有效template<HasSize Container>void print_container_size(const Container& container) { // 输出容器的大小 std::cout << "容器大小: " << container.size() << std::endl;}
// 更复杂的概念组合// 定义一个 Number 概念,要求类型 T 必须是整数类型或浮点类型template<typename T>concept Number = std::integral<T> || std::floating_point<T>;// 使用标准库中的 std::integral 和 std::floating_point 概念进行判断
// 定义一个平方函数 square,接受符合 Number 概念的类型参数template<Number T>T square(T value) { // 返回传入值的平方 return value * value;}
7.2 自动类型推导的改进
现代C++在类型推导方面有很多改进:
#include <iostream> // 引入输入输出流库,用于输出信息#include <vector> // 引入 vector 容器,用于存储动态数组数据#include <map> // 引入 map 容器,尽管在本示例中未被使用,通常用于存储键值对
// C++17 类模板参数推导 (CTAD) 示例int main() { // 不需要显式指定模板参数,编译器会根据初始化列表中的元素类型推导模板参数类型 std::vector vec{1, 2, 3, 4, 5}; // 上述语句中,编译器会推导出 vec 为 std::vector<int>,因为所有元素都是 int 类型// 使用 std::pair,也可利用 CTAD 自动推导类型 std::pair p{42, 3.14}; // 这里 p 被推导为 std::pair<int, double>,因为第一个元素是 int,第二个元素是 double// 定义一个泛型 lambda 表达式,利用 auto 参数来实现模板化的函数 auto lambda = [](auto x, auto y) { return x + y; }; // lambda 是一个泛型函数对象,可以接受任意类型参数,只要它们支持加法运算// 调用 lambda,下面的调用将自动推导出参数类型,并根据实际参数执行相应的加法操作 std::cout << lambda(1, 2) << std::endl; // 传入两个 int 类型的参数,结果为 int 类型相加 std::cout << lambda(1.5, 2.7) << std::endl; // 传入两个 double 类型的参数,结果为 double 类型相加 std::cout << lambda(1, 2.5) << std::endl; // 传入 int 与 double 类型的参数,编译器会进行类型提升,结果为 doublereturn 0; // 程序正常结束}
总结与最佳实践
通过这个教程,我们深入了解了C++模板系统的各个方面。模板是C++中最强大的特性之一,它支持泛型编程、元编程和类型安全的代码复用。
关键要点回顾:
模板基础: 模板允许编写与类型无关的代码,编译器根据使用情况自动生成具体的代码实例。
函数模板: 用于创建泛型函数,支持类型推导和显式类型指定。
类模板: 用于创建泛型类,可以有多个模板参数,包括类型参数和非类型参数。
模板特化:
全特化:为特定类型提供完全不同的实现
偏特化:为模板参数的某些组合提供特殊实现(仅限类模板)
高级技术:
SFINAE:利用替换失败进行条件编译
变参模板:处理可变数量的参数
模板元编程:在编译期进行计算和类型操作
现代特性:
概念 (C++20):为模板参数添加约束
改进的类型推导:CTAD、auto、泛型lambda
使用建议:
优先使用标准库提供的模板和算法
编写模板时要考虑错误信息的可读性
合理使用特化,不要过度复杂化
在现代C++中,优先使用constexpr而不是模板元编程进行编译期计算
使用概念 (如果有C++20支持) 来使模板更易于理解和使用
模板是C++的核心特性,掌握它们将大大提升您的C++编程能力。记住,模板的学习是一个渐进的过程,从简单的泛型函数开始,逐步深入到更高级的技术。
相关文章:

【C++】模板与特化技术全面教程(claude sonnet 4)
第一章:模板的基础概念 (Template Fundamentals) 1.1 什么是模板? 模板 (Template) 是C中的一种泛型编程 (Generic Programming) 机制,它允许我们编写与类型无关的代码。想象一下,如果我们要为不同的数据类型编写相同逻辑的函数&a…...

ABAP设计模式之---“高内聚,低耦合(High Cohesion Low Coupling)”
“高内聚、低耦合”是面向对象编程中非常重要的设计原则,它有助于提高代码的可维护性、扩展性和复用性。 1. 初衷:为什么会有这个原则? 在软件开发中,随着业务需求的复杂化,代码难免会变得越来越庞大。如果开发者将一…...

RagFlow优化代码解析(一)
引子 前文写到RagFlow的环境搭建&推理测试,感兴趣的童鞋可以移步(RagFlow环境搭建&推理测试-CSDN博客)。前文也写过RagFLow参数配置&测试的文档,详见()。很少写关于具体代码的blog,…...

【python与生活】用 Python 从视频中提取音轨:一个实用脚本的开发与应用
在当今数字化的时代,视频内容无处不在。无论是学习教程、会议记录、在线讲座还是娱乐视频,我们每天都会接触到大量的视频资源。有时候,我们可能只对视频中的音频部分感兴趣,比如提取讲座的音频用于后续收听,或者从电影…...

深度强化学习赋能城市消防优化,中科院团队提出DRL新方法破解设施配置难题
在城市建设与发展中,地理空间优化至关重要。从工业园区选址,到公共服务设施布局,它都发挥着关键作用。但传统求解方法存在诸多局限,如今,深度学习技术为其带来了新的转机。 近日,在中国地理学会地理模型与…...
云原生周刊:探索 Gateway API v1.3.0
开源项目推荐 WatchAlert WatchAlert 是一个轻量级、云原生的多数据源监控告警引擎,支持 AI 驱动的智能告警分析,旨在帮助升级您的监控系统架构。该项目基于 Go 和 React 开发,提供了现代化的前后端架构。后端使用 Go 语言,结合…...

008房屋租赁系统技术揭秘:构建智能租赁服务生态
房屋租赁系统技术揭秘:构建智能租赁服务生态 在房地产租赁市场日益活跃的当下,房屋租赁系统成为连接房东与租客的重要数字化桥梁。该系统集成用户管理、房屋信息等多个核心模块,面向管理员、房东和用户三类角色,通过前台展示与后…...
Python训练打卡Day41
简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 卷积操作常见流程如下: 1. 输入 → 卷积层 → Batch…...

spring-boot-admin实现对微服务监控
spring-boot-admin可以对微服务的状态进行监控,步骤如下: 1、添加spring-boot-admin和nacos依赖 <!-- nacos注册中心 --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-n…...
Linux 权限管理入门:从基础到实践
文章目录 引言一、Linux 权限管理概述二、文件权限值的表示方法三、文件访问权限的设置(chmod)四、file指令:快速识别文件类型五、目录的权限六、普通文件的权限七、权限总结八、粘滞位 引言 在 Linux 系统中,权限管理是确保多用…...

Mycat的监控
参考资料: 参考视频 参考博客 Mysql分库分表(基于Mycat)的基本部署 MySQL垂直分库(基于MyCat) Mysql水平分表(基于Mycat)及常用分片规则 视频参考资料及安装包: https://pan.b…...

Glide源码解析
前言 Glide是一款专为Android设计的开源图片加载库。有以下特点:1.支持高效加载网络、本地及资源图片;2.具备良好的缓存策略及生命周期管理策略;3.提供了简易的API和强大的功能。本文将对其源码进行剖析。 基本使用 dependencies {compile …...

7.RV1126-OPENCV cvtColor 和 putText
一.cvtColor 1.作用 cvtColor 是 OPENCV 里面颜色转换的转换函数。能够实现 RGB 图像转换成灰度图、灰度图转换成 RGB 图像、RGB 转换成 HSV 等等 2.API CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0 ); 第一个参数:…...
Android 之 kotlin 语言学习笔记二(编码样式)
参考官方文档:https://developer.android.google.cn/kotlin/style-guide?hlzh-cn#whitespace 1、源文件命名 所有源文件都必须编码为 UTF-8。如果源文件只包含一个顶级类,则文件名应为该类的名称(区分大小写)加上 .kt 扩展名。…...

Redisson单机模式
redisson调用unlock的过程 Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid)框架,提供了分布式和可扩展的数据结构和服务。Redisson 的 unlock 方法用于释放锁。下面是 unlock 方法的调用过程: 获取锁的状…...

数据结构第6章 图(竟成)
第 6 章 图 【考纲内容】 1.图的基本概念 2.图的存储及基本操作:(1) 邻接矩阵法;(2) 邻接表法;(3) 邻接多重表、十字链表 3.图的遍历:(1) 深度优先搜索;(2) 广度优先搜索 4.图的基本应用:(1) 最小 (代价) 生…...

机器人现可完全破解验证码:未来安全技术何去何从?
引言 随着计算机视觉技术的飞速发展,机器学习模型现已能够100%可靠地解决Google的视觉reCAPTCHAv2验证码。这标志着一个时代的结束——自2000年代初以来,CAPTCHA("全自动区分计算机与人类的图灵测试"的缩写)一直是区分…...

CppCon 2014 学习:(Costless)Software Abstractions for Parallel Architectures
硬件和科学计算的演变关系: 几十年来的硬件进步:计算机硬件不断快速发展,从提升单核速度,到多核并行。科学计算的驱动力:科学计算需求推动硬件创新,比如需要更多计算能力、更高性能。当前的解决方案是并行…...

网络爬虫 - App爬虫及代理的使用(十一)
App爬虫及代理的使用 一、App抓包1. App爬虫原理2. reqable的安装与配置1. reqable安装教程2. reqable的配置3. 模拟器的安装与配置1. 夜神模拟器的安装2. 夜神模拟器的配置4. 内联调试及注意事项1. 软件启动顺序2. 开启抓包功能3. reqable面板功能4. 夜神模拟器设置项5. 注意事…...
Kafka集群部署(docker容器方式)SASL认证(zookeeper)
一、服务器环境 序号 部署版本 版本 1 操作系统 CentOS Linux release 7.9.2009 (Core) 2 docker Docker version 20.10.6 3 docker-compose docker-compose version 1.28.2 二、服务规划 序号 服务 名称 端口 1 zookeeper zookeeper 2181,2888,3888 2 ka…...
【python爬虫】利用代理IP爬取filckr网站数据
亮数据官网链接:亮数据官网...

群晖 NAS 如何帮助培训学校解决文件管理难题
在现代教育环境中,数据管理和协同办公的效率直接影响到教学质量和工作流畅性。某培训学校通过引入群晖 NAS,显著提升了部门的协同办公效率。借助群晖的在线协作、自动备份和快照功能,该校不仅解决了数据散乱和丢失的问题,还大幅节…...

NLP学习路线图(十八):Word2Vec (CBOW Skip-gram)
自然语言处理(NLP)的核心挑战在于让机器“理解”人类语言。传统方法依赖独热编码(One-hot Encoding) 表示单词,但它存在严重缺陷:每个单词被视为孤立的符号,无法捕捉词义关联(如“国…...
P1438 无聊的数列/P1253 扶苏的问题
因为这两天在写线性代数的作业,没怎么写题…… P1438 无聊的数列 题目背景 无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天,无聊的 YYB 想出了一道无聊的题:无聊的数列。。。 题目描述 维护一个数列 ai,支持两种操…...

嵌入式学习笔记 - 新版Keil软件模拟时钟Xtal灰色不可更改的问题
在新版Keil软件中,模拟时钟无法修改XTAL频率,默认只能使用12MHz时钟。这是因为Keil MDK从5.36版本开始,参数配置界面不再支持修改系统XTAL频率,XTAL选项变为灰色,无法修改。这会导致在软件仿真时出现时间错误的问题&…...
k8s的出现解决了java并发编程胡问题了
Kubernetes(K8s)作为一种开源的容器编排平台,极大地简化了应用程序的部署、管理和扩展。这不仅解决了很多基础设施方面的问题,也间接解决了Java并发编程中的一些复杂问题。本文将详细探讨Kubernetes是如何帮助解决Java并发编程中的…...
如何利用大语言模型生成特定格式文风的报告类文章
在这个算法渗透万物的时代,我们不再仅仅满足于大语言模型(LLM)能“写”,更追求它能“写出精髓,写出风格”。尤其在专业且高度格式化的报告类文章领域,仅仅是内容正确已远远不够,文风的精准复刻才是决定报告是否“对味儿”、能否被目标受众有效接受的关键。这不再是简单的…...

黑马Java面试笔记之 集合篇(算法复杂度+ArrayList+)
一. 算法复杂度分析 1.1 时间复杂度 时间复杂度分析:来评估代码的执行耗时的 常见的复杂度表示形式 常见复杂度 1.2 空间复杂度 空间复杂度全称是渐进空间复杂度,表示算法占用的额外存储空间与数据规模之间的增长关系 二. 数组 数组(Array&a…...
【从0-1的HTML】第2篇:HTML标签
文章目录 1.标题标签2.段落标签3.文本标签brbstrongsubsup 4.超链接标签5.图片标签6.表格标签7.列表标签有序列表ol无序列表ul定义列表dl 8.表单标签9.音频标签10.视频标签11.HTML元素分类块级元素内联元素 12.HTML布局13.内联框架13.内联框架 1.标题标签 标题标签:…...
从“Bucharest”谈起:词语翻译的音译与意译之路
在翻译中,面对地名、人名或新兴术语时,我们常常会遇到一个抉择:到底是“音译”,保留其原发音风貌,还是“意译”,让它意义通达? 今天我们以“Bucharest”为例,展开一次语言与文化的微…...