name: cpp-templates-metaprogramming user-invocable: false description: 当需要C++模板和元编程时使用,包括模板特化、SFINAE、类型特征和C++20概念。 allowed-tools:
- 读取
- 写入
- 编辑
- 搜索
- 全局
- Bash
C++ 模板和元编程
模板元编程允许编译时计算和代码生成,创建灵活、高效的抽象而无需运行时开销。此技能涵盖函数和类模板、特化、SFINAE、类型特征和现代基于概念的模板约束。
函数模板
函数模板允许编写适用于任何满足要求的类型的通用算法。
#include <iostream>
#include <vector>
#include <string>
// 基本函数模板
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// 多个模板参数
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// 带有非类型参数的模板
template<typename T, size_t N>
size_t array_size(T (&)[N]) {
return N;
}
// 模板重载
template<typename T>
void print(T value) {
std::cout << value << "
";
}
template<typename T>
void print(const std::vector<T>& vec) {
for (const auto& item : vec) {
std::cout << item << " ";
}
std::cout << "
";
}
void function_template_examples() {
auto max_int = maximum(10, 20);
auto max_double = maximum(3.14, 2.71);
auto max_string = maximum(std::string("abc"), std::string("xyz"));
auto sum = add(5, 3.14); // int + double
int arr[] = {1, 2, 3, 4, 5};
std::cout << "数组大小: " << array_size(arr) << "
";
print(42);
print(std::vector<int>{1, 2, 3});
}
类模板
类模板允许创建通用容器和数据结构。
#include <iostream>
#include <stdexcept>
// 基本类模板
template<typename T>
class Stack {
T* data_;
size_t size_;
size_t capacity_;
public:
Stack(size_t capacity = 10)
: data_(new T[capacity])
, size_(0)
, capacity_(capacity) {}
~Stack() {
delete[] data_;
}
void push(const T& value) {
if (size_ >= capacity_) {
resize();
}
data_[size_++] = value;
}
T pop() {
if (size_ == 0) {
throw std::underflow_error("堆栈为空");
}
return data_[--size_];
}
bool empty() const { return size_ == 0; }
size_t size() const { return size_; }
private:
void resize() {
capacity_ *= 2;
T* new_data = new T[capacity_];
for (size_t i = 0; i < size_; ++i) {
new_data[i] = data_[i];
}
delete[] data_;
data_ = new_data;
}
};
// 多个模板参数
template<typename Key, typename Value>
class Pair {
Key key_;
Value value_;
public:
Pair(const Key& k, const Value& v) : key_(k), value_(v) {}
const Key& key() const { return key_; }
const Value& value() const { return value_; }
};
// 带有默认参数的模板
template<typename T, typename Allocator = std::allocator<T>>
class Vector {
// 实现
};
void class_template_examples() {
Stack<int> int_stack;
int_stack.push(1);
int_stack.push(2);
std::cout << int_stack.pop() << "
";
Stack<std::string> str_stack;
str_stack.push("hello");
Pair<std::string, int> p("age", 30);
}
模板特化
模板特化允许为特定类型提供自定义实现。
#include <iostream>
#include <cstring>
// 主模板
template<typename T>
class Container {
T value_;
public:
Container(const T& value) : value_(value) {}
void print() const {
std::cout << "通用: " << value_ << "
";
}
size_t memory_size() const {
return sizeof(T);
}
};
// 对const char*的完全特化
template<>
class Container<const char*> {
const char* value_;
public:
Container(const char* value) : value_(value) {}
void print() const {
std::cout << "C字符串: " << value_ << "
";
}
size_t memory_size() const {
return std::strlen(value_) + 1;
}
};
// 对指针的部分特化
template<typename T>
class Container<T*> {
T* value_;
public:
Container(T* value) : value_(value) {}
void print() const {
std::cout << "指针: " << *value_ << "
";
}
size_t memory_size() const {
return sizeof(T*);
}
};
// 函数模板特化
template<typename T>
bool is_negative(T value) {
return value < 0;
}
template<>
bool is_negative<bool>(bool value) {
return false; // 布尔值不能为负
}
void specialization_examples() {
Container<int> c1(42);
c1.print(); // 通用
Container<const char*> c2("hello");
c2.print(); // C字符串
int x = 10;
Container<int*> c3(&x);
c3.print(); // 指针
}
SFINAE(替换失败不是错误)
SFINAE允许基于类型属性的编译时函数选择。
#include <iostream>
#include <type_traits>
#include <vector>
// 启用如果类型有begin()和end()
template<typename T>
typename std::enable_if<
std::is_same<
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())
>::value
>::type print_container(const T& container) {
std::cout << "容器: ";
for (const auto& item : container) {
std::cout << item << " ";
}
std::cout << "
";
}
// 启用如果类型是算术类型
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type
print_value(T value) {
std::cout << "数字: " << value << "
";
}
// 启用如果类型不是算术类型
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value>::type
print_value(const T& value) {
std::cout << "非数字: " << value << "
";
}
// 使用std::enable_if作为模板参数
template<typename T,
typename = std::enable_if_t<std::is_integral<T>::value>>
T safe_divide(T a, T b) {
if (b == 0) {
throw std::domain_error("除以零");
}
return a / b;
}
// 标签分发(SFINAE的替代方法)
template<typename T>
void process_impl(T value, std::true_type /* is_pointer */) {
std::cout << "处理指针: " << *value << "
";
}
template<typename T>
void process_impl(T value, std::false_type /* is_pointer */) {
std::cout << "处理值: " << value << "
";
}
template<typename T>
void process(T value) {
process_impl(value, std::is_pointer<T>{});
}
void sfinae_examples() {
std::vector<int> vec{1, 2, 3};
print_container(vec);
print_value(42);
print_value(std::string("hello"));
std::cout << safe_divide(10, 2) << "
";
int x = 100;
process(x);
process(&x);
}
类型特征
类型特征提供编译时类型信息和转换。
#include <type_traits>
#include <iostream>
#include <string>
// 使用标准类型特征
template<typename T>
void analyze_type() {
std::cout << "类型分析:
";
std::cout << " 是否是整数: "
<< std::is_integral<T>::value << "
";
std::cout << " 是否是浮点数: "
<< std::is_floating_point<T>::value << "
";
std::cout << " 是否是指针: "
<< std::is_pointer<T>::value << "
";
std::cout << " 是否是const: "
<< std::is_const<T>::value << "
";
std::cout << " 大小: " << sizeof(T) << "
";
}
// 类型转换
template<typename T>
void transform_type() {
using NoCV = std::remove_cv_t<T>;
using NoRef = std::remove_reference_t<T>;
using NoPtr = std::remove_pointer_t<T>;
using AddConst = std::add_const_t<T>;
using AddLRef = std::add_lvalue_reference_t<T>;
std::cout << "移除cv后是否相同: "
<< std::is_same<NoCV, T>::value << "
";
}
// 自定义类型特征
template<typename T>
struct is_string : std::false_type {};
template<>
struct is_string<std::string> : std::true_type {};
template<>
struct is_string<const char*> : std::true_type {};
template<typename T>
inline constexpr bool is_string_v = is_string<T>::value;
// 条件类型
template<typename T>
using MakeUnsigned = std::conditional_t<
std::is_signed<T>::value,
std::make_unsigned_t<T>,
T
>;
// 编译时if(C++17)
template<typename T>
void print_type(const T& value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "整数: " << value << "
";
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "浮点数: " << value << "
";
} else if constexpr (is_string_v<T>) {
std::cout << "字符串: " << value << "
";
} else {
std::cout << "未知类型
";
}
}
void type_traits_examples() {
analyze_type<int>();
analyze_type<const double*>();
print_type(42);
print_type(3.14);
print_type(std::string("hello"));
}
变参模板
变参模板允许函数和类接受任意数量的参数。
#include <iostream>
#include <sstream>
// 基本情况
void print_all() {
std::cout << "
";
}
// 递归变参模板
template<typename T, typename... Args>
void print_all(T first, Args... rest) {
std::cout << first << " ";
print_all(rest...);
}
// 折叠表达式(C++17)
template<typename... Args>
auto sum_all(Args... args) {
return (args + ...);
}
template<typename... Args>
auto multiply_all(Args... args) {
return (args * ... * 1);
}
// 变参类模板
template<typename... Types>
class Tuple;
template<>
class Tuple<> {
public:
static constexpr size_t size = 0;
};
template<typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
Head head_;
public:
static constexpr size_t size = 1 + Tuple<Tail...>::size;
Tuple(Head h, Tail... t)
: Tuple<Tail...>(t...), head_(h) {}
Head& head() { return head_; }
const Head& head() const { return head_; }
Tuple<Tail...>& tail() {
return *this;
}
};
// 参数包扩展
template<typename... Args>
void process_all(Args... args) {
// 在初始化列表中扩展
int dummy[] = { (std::cout << args << " ", 0)... };
(void)dummy; // 抑制未使用警告
}
// 用于编译时迭代的索引序列
template<size_t... Is>
void print_indices(std::index_sequence<Is...>) {
((std::cout << Is << " "), ...);
std::cout << "
";
}
void variadic_examples() {
print_all(1, 2.5, "hello", std::string("world"));
auto total = sum_all(1, 2, 3, 4, 5);
auto product = multiply_all(2, 3, 4);
Tuple<int, double, std::string> t(42, 3.14, "test");
std::cout << "元组大小: " << decltype(t)::size << "
";
print_indices(std::make_index_sequence<5>{});
}
模板元编程
模板元编程使用模板执行编译时计算。
#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;
};
// 编译时斐波那契数列
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;
};
// 类型列表操作
template<typename... Types>
struct TypeList {};
// 获取类型列表大小
template<typename List>
struct Length;
template<typename... Types>
struct Length<TypeList<Types...>> {
static constexpr size_t value = sizeof...(Types);
};
// 获取索引处的元素
template<size_t Index, typename List>
struct At;
template<size_t Index, typename Head, typename... Tail>
struct At<Index, TypeList<Head, Tail...>> {
using type = typename At<Index - 1, TypeList<Tail...>>::type;
};
template<typename Head, typename... Tail>
struct At<0, TypeList<Head, Tail...>> {
using type = Head;
};
// 检查类型是否在列表中
template<typename T, typename List>
struct Contains;
template<typename T>
struct Contains<T, TypeList<>> : std::false_type {};
template<typename T, typename Head, typename... Tail>
struct Contains<T, TypeList<Head, Tail...>>
: Contains<T, TypeList<Tail...>> {};
template<typename T, typename... Tail>
struct Contains<T, TypeList<T, Tail...>> : std::true_type {};
// Constexpr函数(C++11及以后)
constexpr int factorial_constexpr(int n) {
return (n <= 1) ? 1 : n * factorial_constexpr(n - 1);
}
constexpr int fibonacci_constexpr(int n) {
return (n <= 1) ? n : fibonacci_constexpr(n - 1) +
fibonacci_constexpr(n - 2);
}
void metaprogramming_examples() {
// 在编译时计算
constexpr int fact5 = Factorial<5>::value;
constexpr int fib7 = Fibonacci<7>::value;
std::cout << "5! = " << fact5 << "
";
std::cout << "fib(7) = " << fib7 << "
";
using MyTypes = TypeList<int, double, std::string>;
std::cout << "类型列表长度: "
<< Length<MyTypes>::value << "
";
using SecondType = At<1, MyTypes>::type; // double
std::cout << "包含int: "
<< Contains<int, MyTypes>::value << "
";
// 现代constexpr
constexpr int fact6 = factorial_constexpr(6);
std::cout << "6! = " << fact6 << "
";
}
概念(C++20)
概念为模板参数提供命名约束,具有更好的错误消息。
#include <concepts>
#include <iostream>
// 基本概念
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// 带有要求的概念
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
// 带有多个约束的概念
template<typename T>
concept Container = requires(T c) {
typename T::value_type;
typename T::iterator;
{ c.begin() } -> std::same_as<typename T::iterator>;
{ c.end() } -> std::same_as<typename T::iterator>;
{ c.size() } -> std::convertible_to<std::size_t>;
};
// 在函数模板中使用概念
template<Numeric T>
T add(T a, T b) {
return a + b;
}
// 概念作为返回类型约束
template<typename T>
auto square(T x) -> std::same_as<T> auto {
return x * x;
}
// 多个概念约束
template<typename T>
concept Sortable = std::totally_ordered<T> && std::copyable<T>;
template<Sortable T>
void sort_values(std::vector<T>& values) {
std::sort(values.begin(), values.end());
}
// 包含(概念细化)
template<typename T>
concept SignedNumeric = Numeric<T> && std::signed_integral<T>;
template<Numeric T>
void process(T value) {
std::cout << "处理数值
";
}
template<SignedNumeric T>
void process(T value) {
std::cout << "处理有符号数值
";
}
void concepts_examples() {
auto result = add(5, 10); // 正确
auto dresult = add(5.5, 2.3); // 正确
// auto sresult = add("hi", "there"); // 错误
std::vector<int> vec{3, 1, 2};
sort_values(vec);
process(5); // 调用有符号数值版本
process(5.5); // 调用数值版本
}
最佳实践
- 在C++20中,使用概念代替SFINAE以获得更清晰的模板约束
- 为了提高可读性,优先使用
constexpr函数而不是模板元编程 - 使用
std::enable_if_t和类型特征的_v和_t后缀以简化代码 - 即使没有概念,也要清晰地记录模板要求
- 使用
decltype(auto)进行完美的返回类型推断 - 当完全实现不同时,优先使用模板特化而不是SFINAE
- 在可能时,使用折叠表达式代替递归变参模板
- 标记模板函数为
inline或在头文件中定义以避免链接错误 - 使用
static_assert在编译时验证模板参数 - 优先使用标准库类型特征而不是自定义实现
常见陷阱
- 忘记在头文件中定义模板成员函数,导致链接错误
- 没有适当基类的无限模板递归
- 难以阅读和维护的复杂SFINAE表达式
- 在引用依赖类型时没有使用
typename关键字 - 由于大型模板的不必要实例化导致模板膨胀
- 模板特化中的循环依赖
- 当多个SFINAE条件匹配时,函数重载不明确
- 复杂模板元编程导致的过度编译时间
- 没有将模板
constexpr函数标记为constexpr - 当运行时多态更简单且足够时使用模板
何时使用模板和元编程
在需要时使用模板和元编程:
- 适用于多种类型的通用算法
- 编译时计算和代码生成
- 无运行时成本的零开销抽象
- 具有强编译时检查的类型安全接口
- 适用于任何类型的容器和数据结构
- 用于领域特定语言的表达式模板
- 具有编译时配置的策略驱动设计
- 消除相似实现之间的代码重复
- 无虚函数开销的静态多态性
- 现代C++库中灵活、可组合的组件