模板元编程
00:00
模板特化、SFINAE与概念
概述
模板元编程(Template Metaprogramming, TMP)是利用 C++ 模板系统在编译期执行计算和代码生成的编程范式。模板是图灵完备的,意味着理论上任何可计算的问题都可以在编译期解决。从 C++11 的 SFINAE 到 C++17 的 constexpr if,再到 C++20 的概念(Concepts),模板元编程的语法越来越简洁,但核心思想始终不变:将运行时逻辑转移到编译期。
基础概念
模板元编程的演进
| 标准 | 特性 | 说明 |
|---|---|---|
| C++98 | 模板特化、SFINAE | 基础元编程 |
| C++11 | type_traits、enable_if | 标准化类型操作 |
| C++14 | 变量模板 | 更简洁的元函数 |
| C++17 | constexpr if、void_t | 消除 SFINAE 的复杂写法 |
| C++20 | 概念(Concepts) | 约束模板参数的标准化方式 |
元函数
元函数是在编译期”计算”的类型或值的模板。传统元函数通过模板特化实现,现代写法使用变量模板:
// 传统写法:通过结构体的静态成员
template<typename T>
struct is_pointer { static constexpr bool value = false; };
template<typename T>
struct is_pointer<T*> { static constexpr bool value = true; };
// C++14 写法:变量模板
template<typename T>
constexpr bool is_pointer_v = false;
template<typename T>
constexpr bool is_pointer_v<T*> = true;
// 使用
static_assert(is_pointer<int*>::value); // 传统
static_assert(is_pointer_v<int*>); // C++14
快速上手
模板特化
#include <iostream>
#include <type_traits>
// 主模板:通用实现
template<typename T>
struct TypeName {
static std::string get() { return "unknown"; }
};
// 特化:针对特定类型
template<>
struct TypeName<int> {
static std::string get() { return "int"; }
};
template<>
struct TypeName<double> {
static std::string get() { return "double"; }
};
// 偏特化:针对指针类型
template<typename T>
struct TypeName<T*> {
static std::string get() { return TypeName<T>::get() + "*"; }
};
// 使用
std::cout << TypeName<int>::get() << std::endl; // "int"
std::cout << TypeName<double*>::get() << std::endl; // "double*"
SFINAE(替换失败不是错误)
#include <type_traits>
#include <iostream>
// SFINAE:当模板参数替换失败时,不产生编译错误,而是从重载集中移除
// 方式一:enable_if 作为默认模板参数
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T process(T value) {
std::cout << "整数处理" << std::endl;
return value * 2;
}
// 方式二:enable_if 作为返回类型
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
process(T value) {
std::cout << "浮点处理" << std::endl;
return value * 2.0;
}
// 使用
process(42); // 调用整数版本
process(3.14); // 调用浮点版本
// process("hello"); // 编译错误:无匹配的重载
C++20 概念
#include <concepts>
#include <iostream>
// 定义概念:约束模板参数
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// 使用概念约束模板参数
template<Addable T>
T add(T a, T b) { return a + b; }
// 更简洁的 requires 子句
template<typename T>
requires Numeric<T>
T multiply(T a, T b) { return a * b; }
// 简写形式
T divide(Numeric auto a, Numeric auto b) { return a / b; }
// 使用
add(1, 2); // 正确:int 满足 Addable
add(1.5, 2.5); // 正确:double 满足 Addable
// add("a", "b"); // 错误:string 不满足 Addable
详细用法
void_t 检测成员(C++17)
#include <type_traits>
// 检测类型 T 是否有 toString() 成员函数
template<typename T, typename = void>
struct has_toString : std::false_type {};
template<typename T>
struct has_toString<T, std::void_t<decltype(std::declval<T>().toString())>>
: std::true_type {};
// C++17 变量模板
template<typename T>
constexpr bool has_toString_v = has_toString<T>::value;
// 使用
class WithToString {
public:
std::string toString() const { return "with"; }
};
class WithoutToString {};
static_assert(has_toString_v<WithToString>);
static_assert(!has_toString_v<WithoutToString>);
constexpr if 简化条件编译
#include <type_traits>
#include <string>
// C++17 constexpr if 替代 SFINAE
template<typename T>
std::string toString(const T& value) {
if constexpr (std::is_same_v<T, std::string>) {
return value;
} else if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(value);
} else if constexpr (std::is_pointer_v<T>) {
return "pointer";
} else {
return static_cast<std::string>(value);
}
}
// 未选中的分支不会被实例化,不会产生编译错误
编译期类型列表
#include <type_traits>
// 类型列表
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<typename List>
struct Head;
template<typename First, typename... Rest>
struct Head<TypeList<First, Rest...>> {
using type = First;
};
// 尾部列表
template<typename List>
struct Tail;
template<typename First, typename... Rest>
struct Tail<TypeList<First, Rest...>> {
using type = TypeList<Rest...>;
};
// 使用
using MyList = TypeList<int, double, std::string>;
static_assert(Length<MyList>::value == 3);
static_assert(std::is_same_v<Head<MyList>::type, int>);
static_assert(std::is_same_v<Tail<MyList>::type, TypeList<double, std::string>>);
常见场景
编译期接口检测
#include <concepts>
// C++20: 使用概念检测接口
template<typename T>
concept Serializable = requires(const T& t, std::ostream& os) {
{ t.serialize(os) } -> std::same_as<void>;
{ T::deserialize(std::declval<std::istream&>()) } -> std::same_as<T>;
};
// 约束模板
template<Serializable T>
void saveToFile(const T& obj, const std::string& path) {
std::ofstream file(path);
obj.serialize(file);
}
条件性成员函数
#include <type_traits>
#include <concepts>
template<typename T>
class Optional {
alignas(T) unsigned char storage_[sizeof(T)];
bool has_value_ = false;
public:
// 仅当 T 可默认构造时提供该函数
template<typename U = T>
requires std::default_initializable<U>
void emplace() {
new (storage_) T();
has_value_ = true;
}
// 仅当 T 可比较时提供比较运算符
template<typename U = T>
requires std::equality_comparable<U>
bool operator==(const Optional& other) const {
if (has_value_ != other.has_value_) return false;
if (!has_value_) return true;
return **this == *other;
}
};
注意事项
- 模板元编程的编译错误信息通常很长且难以理解,使用概念可以显著改善错误信息质量
- 过度使用 SFINAE 会增加编译时间,C++20 概念是更优的替代方案
- constexpr if 比传统 SFINAE 更简洁,优先使用
- 模板代码应放在头文件中,因为模板实例化需要完整的定义
- 注意模板的编译开销,避免不必要的模板实例化
- 使用 static_assert 在模板中提供清晰的错误信息
进阶用法
C++20 requires 表达式
#include <concepts>
// 复杂的概念定义
template<typename C>
concept Container = requires(C c) {
// 必须有 size() 方法
{ c.size() } -> std::convertible_to<size_t>;
// 必须有 begin() 和 end()
{ c.begin() } -> std::input_or_output_iterator;
{ c.end() } -> std::input_or_output_iterator;
// 必须有 value_type 类型别名
typename C::value_type;
};
// 嵌套约束
template<typename C>
concept SortableContainer = Container<C> && requires(C c) {
// 必须支持随机访问
{ c.begin() } -> std::random_access_iterator;
// 元素必须可比较
requires std::totally_ordered<typename C::value_type>;
};
// 使用
template<SortableContainer C>
void sort(C& container) {
std::sort(container.begin(), container.end());
}
编译期字符串哈希
// 使用模板元编程实现编译期字符串哈希
template<size_t N>
struct ConstexprString {
char data[N]{};
constexpr ConstexprString(const char (&str)[N]) {
for (size_t i = 0; i < N; ++i) data[i] = str[i];
}
constexpr size_t hash() const {
size_t h = 0;
for (size_t i = 0; i < N - 1; ++i) {
h = h * 31 + static_cast<size_t>(data[i]);
}
return h;
}
};
// 编译期字符串匹配
constexpr ConstexprString cmd = "start";
static_assert(cmd.hash() != 0);