前置知识: C++

模板元编程

00:00
3 min Advanced 2026/6/14

模板特化、SFINAE与概念

概述

模板编程(Template Metaprogramming, TMP)是利用 C++ 模板系统在编译期执行计算和代码生成的编程范式。模板是图灵完备的,意味着理论上任何可计算的问题都可以在编译期解决。从 C++11 的 SFINAE 到 C++17 的 constexpr if,再到 C++20 的概念(Concepts),模板元编程的语法越来越简洁,但核心思想始终不变:将运行时逻辑转移到编译期。

基础概念

模板元编程的演进

标准特性说明
C++98模板特化、SFINAE基础元编程
C++11type_traits、enable_if标准化类型操作
C++14变量模板更简洁的函数
C++17constexpr 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);

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式