C++: 模板元编程入门

最后更新:2026-08-26

第46课我们学了智能指针进阶。

现在,我们要接触C++的"黑魔法"——模板元编程

模板元编程是在编译期执行代码的技术,可以大幅提升运行时性能。


1. 模板元编程概述

(1) 1.1 什么是模板元编程?

模板元编程(Template Metaprogramming,TMP)是利用模板在编译期执行计算的技术。

特点:


(2) 1.2 为什么要用模板元编程?

优势 说明
性能 编译期计算,运行时更快
类型安全 编译期类型检查
泛型 写出真正的泛型代码


2. 编译期计算

(1) 2.1 编译期阶乘

示例:用模板计算阶乘(难度⭐⭐⭐)

▶ 示例 1:代码示例(难度⭐)

CPP
#include <iostream>

// 主模板
template<int N>
struct Factorial {
 static const int value = N * FactorialN-1::value;
};

// 特化:终止条件
template<>
struct Factorial<0> {
 static const int value = 1;
};

int main() {
 std::cout << "5! = " << Factorial<5>::value << std::endl; // 120
 // 编译期计算出结果,运行时直接用
 
 return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
5! = 

▶ 示例 2:代码示例(难度⭐)

💡 提示:


(2) 2.2 constexpr函数(C++11,推荐)

C++11引入了 constexpr,让编译期计算更简单。

示例:用constexpr计算阶乘(难度⭐⭐)

CPP
#include <iostream>

constexpr int factorial(int n) {
 return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
 constexpr int result = factorial(5); // 编译期计算
 std::cout << "5! = " << result << std::endl; // 120
 
 return 0;
}

输出:

TEXT 📖 仅展示
5! = 

💡 提示:



3. 类型萃取

(1) 3.1 什么是类型萃取?

类型萃取(Type Traits)是在编译期查询或修改类型信息的技术。

示例:用type_traits判断类型(难度⭐⭐)

CPP
#include <iostream>
#include <type_traits>

int main() {
 std::cout << std::is_integralint::value << std::endl; // 1(true)
 std::cout << std::is_integraldouble::value << std::endl; // 0(false)
 std::cout << std::is_pointer<int*>::value << std::endl; // 1(true)
 
 return 0;
}

(2) 3.2 自定义类型萃取

示例:判断是否有成员函数(难度⭐⭐⭐⭐)

CPP
#include <iostream>
#include <type_traits>

// 检测是否有serialize成员函数
template<typename T>
struct has_serialize {
private:
 template<typename U>
 static auto test(int) -> decltype(std::declvalU().serialize(), std::true_type{});
 
 template<typename U>
 static std::false_type test(...);
 
public:
 static const bool value = decltype(testT(0))::value;
};

struct Person {
 void serialize() {}
};

int main() {
 std::cout << has_serializePerson::value << std::endl; // 1(true)
 std::cout << has_serializeint::value << std::endl; // 0(false)
 
 return 0;
}


4. SFINAE

(1) 4.1 什么是SFINAE?

SFINAE(Substitution Failure Is Not An Error):模板参数替换失败不是错误,编译器会尝试其他重载。

用途: 根据类型特性选择不同的函数重载


(2) 4.2 示例:SFINAE选择重载(难度⭐⭐⭐⭐)

CPP
#include <iostream>
#include <type_traits>
#include <string>

// 版本1:适用于整数类型
template<typename T>
typename std::enable_if<std::is_integralT::value, std::string>::type
toString(T value) {
 return std::to_string(value) + " (整数)";
}

// 版本2:适用于其他类型
template<typename T>
typename std::enable_if<!std::is_integralT::value, std::string>::type
toString(T value) {
 return "非整数";
}

int main() {
 std::cout << toString(42) << std::endl; // 42 (整数)
 std::cout << toString(3.14) << std::endl; // 非整数
 
 return 0;
}

输出:

TEXT 📖 仅展示
(程序输出)


5. 可变参数模板

(1) 5.1 什么是可变参数模板?

可变参数模板(Variadic Template)允许模板接受任意数量的参数。

示例:打印任意数量参数(难度⭐⭐⭐)

CPP
#include <iostream>

// 递归终止函数
void print() {
 std::cout << std::endl;
}

// 递归打印
template<typename T, typename... Args>
void print(T first, Args... rest) {
 std::cout << first << " ";
 print(rest...); // 递归调用
}

int main() {
 print(1, 2.5, "hello", 'a');
 // 输出:1 2.5 hello a
 
 return 0;
}


6. C++17折叠表达式

(1) 6.1 基本用法

C++17引入了折叠表达式(Fold Expression),简化可变参数模板。

示例:求和(难度⭐⭐)

CPP
#include <iostream>

template<typename... Args>
auto sum(Args... args) {
 return (args + ...); // 折叠表达式
}

int main() {
 std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
 return 0;
}

▶ 示例 3:constexpr 编译期计算(难度⭐)

CPP
#include <iostream>

// constexpr 函数在编译期求值
constexpr int square(int n) {
    return n * n;
}

constexpr int cube(int n) {
    return n * n * n;
}

int main() {
    // 编译期计算,直接嵌入结果
    constexpr int sq = square(5);
    constexpr int cb = cube(3);

    std::cout << "5^2 = " << sq << std::endl;
    std::cout << "3^3 = " << cb << std::endl;

    // 静态断言验证编译期求值
    static_assert(sq == 25, "square(5) should be 25");
    static_assert(cb == 27, "cube(3) should be 27");

    return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
5^2 = 
3^3 = 

❓ 常见问题

Q:模板元编程难学吗? A:难。建议先掌握基础模板,再学元编程。大多数项目不需要元编程。


Q 什么时候用模板元编程?
A - 写库(如STL) - 需要极致性能 - 需要编译期类型检查

Q constexpr能替代模板元编程吗?
A 部分可以。C++14/17扩展了 constexpr,很多编译期计算可以用 constexpr 函数实现。

📖 小节

知识点 要点
编译期计算 模板递归或constexpr
类型萃取 type_traits
SFINAE 根据类型选择重载
可变参数模板 接受任意数量参数
折叠表达式 C++17,简化可变参数

📝 作业

  1. **基础题 (Difficulty ⭐):编写一个 constexpr 函数计算阶乘,在编译期求值。在 static_assert 中验证结果。

  2. **进阶题 (Difficulty ⭐⭐):用 std::enable_if 实现一个函数模板,仅当 T 是整数类型时才启用。对浮点类型应编译报错。

  3. **挑战题 (Difficulty ⭐⭐⭐):用可变参数模板实现一个 print_all 函数,接受任意数量和类型的参数,逐个输出。提示:用递归展开或折叠表达式(C++17)。



下一课:C++17/20新特性(#48)

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏