Ch 13: 元编程与模板
- 理解模板基础与类型参数化
- 掌握 type_traits 类型特征
- 学会 C++20 Concepts 约束模板
- 理解编译期计算(constexpr)
- 了解模板元编程基本模式
13.1 Python 类型系统回顾
Section titled “13.1 Python 类型系统回顾”Python 是动态类型语言,类型检查发生在运行时:
# type() - 获取对象类型type(42) # <class 'int'>type("hello") # <class 'str'>type([1, 2, 3]) # <class 'list'>
# isinstance() - 类型检查(更推荐)isinstance(42, int) # Trueisinstance("hello", str) # Trueisinstance([1, 2], (list, tuple)) # True(多类型检查)
# type() 用于类型比较type(obj) == inttype(obj) == list
# 运行时泛型from typing import List, Dict, Optional, Union
def process(items: List[int]) -> Dict[str, int]: result = {} for i, item in enumerate(items): result[f"item_{i}"] = item * 2 return result
# Python 没有编译时类型检查# 类型注解只是提示,不影响运行Python 3.12 引入了 type 语法作为类型别名:
# Python 3.12+type Point = tuple[float, float]type Matrix = list[list[float]]13.2 C++ 类型信息
Section titled “13.2 C++ 类型信息”C++ 提供编译时和运行时两种类型信息机制:
13.2.1 typeid 与运行时类型信息
Section titled “13.2.1 typeid 与运行时类型信息”#include <typeinfo>#include <iostream>#include <string>
class Shape {public: virtual ~Shape() = default;};
class Circle : public Shape {public: double radius = 1.0;};
class Rectangle : public Shape {public: double width, height;};
void typeid_demo() { Shape* circle = new Circle(); Shape* rect = new Rectangle();
// typeid 运行时获取类型信息 std::cout << typeid(*circle).name() << std::endl; // 运行时多态
// 比较类型 if (typeid(*circle) == typeid(Circle)) { std::cout << "It's a Circle" << std::endl; }
// 注意:typeid 需要虚函数表才能正确工作 // 对于非多态类型,直接获取 type_info
delete circle; delete rect;}13.2.2 type_traits 与编译时类型特征
Section titled “13.2.2 type_traits 与编译时类型特征”type_traits 提供编译时类型查询和转换:
#include <type_traits>#include <iostream>#include <vector>#include <string>
void type_traits_demo() { // 基本类型查询 static_assert(std::is_integral_v<int>); // int 是整数类型 static_assert(std::is_floating_point_v<double>); // double 是浮点 static_assert(std::is_pointer_v<int*>); // 指针类型 static_assert(std::is_class_v<std::vector<int>>); // 类类型 static_assert(std::is_same_v<int, int>); // 类型相同 static_assert(!std::is_same_v<int, long>); // int 和 long 不同
// 类型转换 using ptr = std::add_pointer_t<int>; // int* using cref = std::add_const_t<int>; // const int using rref = std::add_rvalue_reference_t<int>; // int&& using lref = std::add_lvalue_reference_t<int>; // int&
// 移除修饰符 using nude = std::remove_cv_t<const volatile int>; // int using noptr = std::remove_pointer_t<int*>; // int
// 类型转换 using from_int = std::make_unsigned_t<int>; // unsigned int using to_int = std::decay_t<int&>; // int
std::cout << "Type traits compile-time checks passed" << std::endl;}13.2.3 type_traits 实用工具
Section titled “13.2.3 type_traits 实用工具”#include <type_traits>
// enable_if - 条件启用函数模板template<typename T>std::enable_if_t<std::is_integral_v<T>, T>add(T a, T b) { return a + b;}
// enable_if_t 多参数版本template<typename T>T multiply(T a, T b) { static_assert(std::is_arithmetic_v<T>, "Must be arithmetic type"); return a * b;}
// conditional_t - 类型选择using type = std::conditional_t<sizeof(int) == 4, int, long>;
// 常见 use case:选择合适的返回类型template<typename T>auto square(T x) -> std::conditional_t<std::is_integral_v<T>, long long, double> { return static_cast<double>(x * x);}
// void_t - 检测合法表达式template<typename, typename = void>struct has_begin : std::false_type {};
template<typename T>struct has_begin<T, std::void_t<decltype(std::declval<T>().begin())>> : std::true_type {};
// 检测类是否有特定成员template<typename T>constexpr bool is_range_v = requires { std::ranges::range<T>;};13.3 模板基础
Section titled “13.3 模板基础”13.3.1 函数模板
Section titled “13.3.1 函数模板”模板允许编写与类型无关的代码:
#include <iostream>
// 函数模板 - 通用的最大值函数template<typename T>T max(T a, T b) { return a > b ? a : b;}
// 显式实例化template int max(int, int); // 实例化为 int 版本template double max(double, double);
// 隐式实例化(编译器自动推导)int m1 = max(1, 2); // T = intdouble m2 = max(3.14, 2.71); // T = doublestd::string m3 = max("apple", "banana"); // T = std::string
// 模板参数推导template<typename T, typename U>auto min(T a, U b) { return a < b ? a : b;}
auto result = min(3, 2.14); // T=int, U=double,返回类型有点复杂13.3.2 类模板
Section titled “13.3.2 类模板”#include <vector>#include <memory>#include <stdexcept>
// 类模板 - 泛型栈template<typename T>class Stack {private: std::vector<T> data_;
public: void push(const T& item) { data_.push_back(item); }
void push(T&& item) { data_.push_back(std::move(item)); }
T pop() { if (data_.empty()) { throw std::out_of_range("Stack empty"); } T item = std::move(data_.back()); data_.pop_back(); return item; }
const T& top() const { if (data_.empty()) { throw std::out_of_range("Stack empty"); } return data_.back(); }
bool empty() const { return data_.empty(); }
size_t size() const { return data_.size(); }};
// 使用Stack<int> int_stack;int_stack.push(42);int_stack.push(13);int top = int_stack.pop(); // 13
Stack<std::string> str_stack;str_stack.push("hello");str_stack.push("world");13.3.3 模板参数默认值
Section titled “13.3.3 模板参数默认值”#include <vector>#include <memory>
template<typename T = int, typename Container = std::vector<T>>class Stack {private: Container data_;
public: void push(const T& item) { data_.push_back(item); }
T pop() { auto item = data_.back(); data_.pop_back(); return item; }};
// 使用默认参数Stack<> default_stack; // Stack<int, std::vector<int>>
// 指定容器类型Stack<double, std::vector<double>> double_stack;Stack<int, std::deque<int>> deque_stack;13.3.4 非类型模板参数
Section titled “13.3.4 非类型模板参数”#include <array>#include <iostream>
// 非类型参数 - 固定大小的数组template<typename T, size_t N>class FixedArray {private: std::array<T, N> data_; size_t size_ = 0;
public: void add(const T& item) { if (size_ < N) { data_[size_++] = item; } }
T get(size_t i) const { return data_[i]; }
size_t size() const { return size_; } size_t capacity() const { return N; }};
FixedArray<int, 100> arr;arr.add(42);
// 模板参数要求template<int N> // 可以是整型void print_array_size() { std::cout << "Array size: " << N << std::endl;}
// 限制:必须是编译期常量// 浮点数、字符串不能作为非类型参数(C++20 有所放松)template<const int* P> // 可以是指针void use_pointer() { // 使用 P 指向的常量}13.4 C++20 Concepts
Section titled “13.4 C++20 Concepts”Concepts 是 C++20 引入的模板约束机制,让模板错误信息更清晰:
13.4.1 预定义 Concepts
Section titled “13.4.1 预定义 Concepts”C++20 标准库提供了一系列预定义 Concepts:
#include <concepts>#include <iostream>#include <string>
// 标准 Concepts 示例template<std::integral T>T add_integers(T a, T b) { return a + b;}
template<std::floating_point T>T add_floats(T a, T b) { return a + b;}
template<std::movable T>void move_object(T& dest, T& src) { dest = std::move(src);}
// 其他常用 Conceptstemplate<std::copyable T>void copy_object(T& dest, const T& src) { dest = src;}
template<std::swappable T>void swap_elements(T& a, T& b) { std::swap(a, b);}
template<std::totally_ordered T>T max(T a, T b) { return a > b ? a : b;}
void concepts_demo() { // OK std::cout << add_integers(1, 2) << std::endl; std::cout << add_floats(3.14, 2.71) << std::endl;
// 编译错误!现在错误信息清晰多了 // add_integers("a", "b"); // 错误:std::string 不是 integral}13.4.2 自定义 Concepts
Section titled “13.4.2 自定义 Concepts”#include <concepts>#include <vector>#include <list>#include <iostream>
// 定义自己的 Concepttemplate<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>concept Iterable = requires(T t) { t.begin(); t.end();};
template<typename T>concept Addable = requires(T a, T b) { a + b; // 必须支持 + 运算};
template<typename T>concept Comparable = requires(T a, T b) { { a < b } -> std::convertible_to<bool>; { a == b } -> std::convertible_to<bool>;};
// 使用自定义 Conceptstemplate<Numeric T>T double_value(T x) { return x * 2;}
template<Iterable T>size_t count_elements(const T& container) { return std::distance(container.begin(), container.end());}
template<Addable T>T add_all(const std::vector<T>& v) { T sum = T{}; for (const auto& item : v) { sum = sum + item; } return sum;}
void custom_concepts_demo() { std::cout << double_value(42) << std::endl; // 84 std::cout << double_value(3.14) << std::endl; // 6.28
std::vector<int> v{1, 2, 3}; std::cout << count_elements(v) << std::endl; // 3
std::list<double> lst{1.1, 2.2, 3.3}; std::cout << count_elements(lst) << std::endl; // 3
// std::string 支持 + std::string s1 = "Hello, ", s2 = "World!"; // cout << double_value(s1); // 编译错误}13.4.3 Concepts 的 requires 子句
Section titled “13.4.3 Concepts 的 requires 子句”#include <concepts>#include <type_traits>
// 简化的 requires 子句template<typename T> requires std::is_integral_v<T>T increment(T x) { return x + 1;}
// 更复杂的约束template<typename T> requires std::integral<T> || std::floating_point<T>T absolute_value(T x) { return x < T{} ? -x : x;}
// 尾部 requires 子句template<typename T>T squared(T x) requires std::is_arithmetic_v<T>{ return x * x;}
// 在类模板中使用template<std::ranges::range T>class Adapter { // T 必须满足 range 约束};13.4.4 C++20 标准 Concepts 一览
Section titled “13.4.4 C++20 标准 Concepts 一览”| Concept | 要求 |
|---|---|
std::same_as<T, U> | T 与 U 是相同类型 |
std::derived_from<T, U> | T 派生自 U |
std::convertible_to<T, U> | T 可转换为 U |
std::integral<T> | T 是整数类型 |
std::floating_point<T> | T 是浮点类型 |
std::movable<T> | T 可移动 |
std::copyable<T> | T 可拷贝 |
std::swappable<T> | T 可交换 |
std::ranges::range<T> | T 是范围 |
std::input_range<T> | T 是输入范围 |
std::forward_range<T> | T 是前向范围 |
std::bidirectional_range<T> | T 是双向范围 |
std::random_access_range<T> | T 是随机访问范围 |
13.5 编译期计算
Section titled “13.5 编译期计算”13.5.1 constexpr 函数
Section titled “13.5.1 constexpr 函数”C++14 增强了 constexpr 函数的能力:
#include <iostream>
// C++14 constexpr - 可以有分支和循环constexpr int factorial(int n) { int result = 1; for (int i = 2; i <= n; ++i) { result *= i; } return result;}
// C++17 constexpr 增强了更多特性constexpr int fibonacci(int n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
// C++20 constexpr 支持更多constexpr bool is_prime(int n) { if (n < 2) return false; for (int i = 2; i * i <= n; ++i) { if (n % i == 0) return false; } return true;}
void constexpr_demo() { // 编译期计算 static_assert(factorial(5) == 120); // 编译时验证 static_assert(fibonacci(10) == 55); static_assert(is_prime(17));
// 也可以运行时计算 std::cout << factorial(5) << std::endl; // 120 std::cout << fibonacci(10) << std::endl; // 55 std::cout << is_prime(17) << std::endl; // 1 (true)}13.5.2 consteval(C++20)
Section titled “13.5.2 consteval(C++20)”consteval 强制函数在编译期求值:
#include <iostream>
// consteval - 必须编译期求值consteval int square(int x) { return x * x;}
// constexpr - 可以编译期或运行期constexpr int cube(int x) { return x * x * x;}
void consteval_demo() { // OK static_assert(square(5) == 25); // 编译期 int runtime_val = 10; int result = square(runtime_val); // 编译期求值,因为参数是常量
// constexpr 允许两种方式 static_assert(cube(3) == 27); // 编译期 int r = cube(3); // 运行期(也可以编译期)
// 错误示例 // int n; // std::cin >> n; // square(n); // 错误:consteval 不能运行时求值}13.5.3 编译期字面量
Section titled “13.5.3 编译期字面量”#include <array>#include <iostream>
// 编译期字面量数组constexpr std::array<int, 10> fibonacci_array() { std::array<int, 10> arr{}; arr[0] = 0; arr[1] = 1; for (int i = 2; i < 10; ++i) { arr[i] = arr[i-1] + arr[i-2]; } return arr;}
void literal_demo() { constexpr auto fib = fibonacci_array(); static_assert(fib[5] == 5); static_assert(fib[9] == 34);}13.6 模板元编程模式
Section titled “13.6 模板元编程模式”13.6.1 类型列表
Section titled “13.6.1 类型列表”#include <type_traits>#include <tuple>
// 类型列表template<typename... Ts>struct TypeList { static constexpr size_t size = sizeof...(Ts);};
// 获取类型列表的元素template<typename TL, size_t I>struct TypeListElement;
template<typename T, typename... Ts>struct TypeListElement<TypeList<T, Ts...>, 0> { using type = T;};
template<typename T, typename... Ts, size_t I>struct TypeListElement<TypeList<T, Ts...>, I> { using type = typename TypeListElement<TypeList<Ts...>, I-1>::type;};
using MyTypes = TypeList<int, double, std::string, char>;
using T0 = typename TypeListElement<MyTypes, 0>::type; // intusing T2 = typename TypeListElement<MyTypes, 2>::type; // std::string13.6.2 条件类型
Section titled “13.6.2 条件类型”#include <type_traits>
// IfThenElsetemplate<bool Condition, typename TrueType, typename FalseType>struct IfThenElse;
template<typename TrueType, typename FalseType>struct IfThenElse<true, TrueType, FalseType> { using type = TrueType;};
template<typename TrueType, typename FalseType>struct IfThenElse<false, TrueType, FalseType> { using type = FalseType;};
template<typename T>struct RemovePointer { using type = T;};
template<typename T>struct RemovePointer<T*> { using type = T;};
// 使用using NonPointer = typename IfThenElse< std::is_pointer_v<int*>, int, int*>::type; // inttypeid提供运行时类型信息,需要虚函数表std::type_traits提供编译时类型特征查询- 模板是 C++ 泛型编程的基础
- 函数模板支持类型推导,类模板支持默认参数
- C++20 Concepts 约束模板参数,让错误信息更清晰
constexpr函数可在编译期求值consteval强制编译期求值- 模板元编程利用编译器计算能力
下章预告:ch14 C++20/23 新特性。