Skip to content

Ch 13: 元编程与模板

  • 理解模板基础与类型参数化
  • 掌握 type_traits 类型特征
  • 学会 C++20 Concepts 约束模板
  • 理解编译期计算(constexpr)
  • 了解模板元编程基本模式

Python 是动态类型语言,类型检查发生在运行时:

# type() - 获取对象类型
type(42) # <class 'int'>
type("hello") # <class 'str'>
type([1, 2, 3]) # <class 'list'>
# isinstance() - 类型检查(更推荐)
isinstance(42, int) # True
isinstance("hello", str) # True
isinstance([1, 2], (list, tuple)) # True(多类型检查)
# type() 用于类型比较
type(obj) == int
type(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]]

C++ 提供编译时和运行时两种类型信息机制:

#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;
}
#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>;
};

模板允许编写与类型无关的代码:

#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 = int
double m2 = max(3.14, 2.71); // T = double
std::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,返回类型有点复杂
#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");
#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;
#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 指向的常量
}

Concepts 是 C++20 引入的模板约束机制,让模板错误信息更清晰:

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);
}
// 其他常用 Concepts
template<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
}
#include <concepts>
#include <vector>
#include <list>
#include <iostream>
// 定义自己的 Concept
template<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>;
};
// 使用自定义 Concepts
template<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); // 编译错误
}
#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 约束
};
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 是随机访问范围

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)
}

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 不能运行时求值
}
#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);
}
#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; // int
using T2 = typename TypeListElement<MyTypes, 2>::type; // std::string
#include <type_traits>
// IfThenElse
template<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; // int
  • typeid 提供运行时类型信息,需要虚函数表
  • std::type_traits 提供编译时类型特征查询
  • 模板是 C++ 泛型编程的基础
  • 函数模板支持类型推导,类模板支持默认参数
  • C++20 Concepts 约束模板参数,让错误信息更清晰
  • constexpr 函数可在编译期求值
  • consteval 强制编译期求值
  • 模板元编程利用编译器计算能力

下章预告:ch14 C++20/23 新特性。