模板元编程入门

内容简介

模板元编程是利用模板在编译期计算类型和值。它不是普通业务代码的第一选择,但很多标准库工具、类型萃取、编译期优化和泛型框架都依赖它。

核心意义

普通程序在运行期计算:

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

模板元编程可以在编译期计算:

template <int N>
struct Factorial {
    static constexpr int value = N * Factorial<N - 1>::value;
};
 
template <>
struct Factorial<0> {
    static constexpr int value = 1;
};
 
static_assert(Factorial<5>::value == 120);

类型作为计算对象

模板元编程常常不是算数字,而是“根据类型产生类型”。

template <typename T>
struct AddPointer {
    using type = T*;
};
 
using IntPtr = AddPointer<int>::type;

现代 C++ 常用别名模板简化:

template <typename T>
using AddPointerT = T*;

std::integral_constant

标准库用 std::integral_constant 表示编译期常量:

using TrueType = std::integral_constant<bool, true>;
using FalseType = std::integral_constant<bool, false>;

std::true_typestd::false_type 就是它的常用别名。

类型萃取的基本形态

自己实现一个简化版 is_pointer

template <typename T>
struct IsPointer : std::false_type {
};
 
template <typename T>
struct IsPointer<T*> : std::true_type {
};
 
static_assert(IsPointer<int*>::value);
static_assert(!IsPointer<int>::value);

这就是“主模板 + 偏特化”的典型模式。

现代写法

很多传统模板元编程可以被更清楚的现代写法替代。

template <typename T>
void print(const T& value)
{
    if constexpr (std::is_pointer_v<T>) {
        if (value) {
            std::cout << *value << '\n';
        }
    } else {
        std::cout << value << '\n';
    }
}

最佳代码实践

  • 普通业务代码不要为了炫技写模板元编程。
  • 使用标准库类型萃取,不要重复造轮子。
  • C++17 后优先考虑 if constexpr
  • C++20 后优先用 concepts 表达类型约束。
  • 模板元编程代码要用 static_assert 写清预期。

错误用法

template <typename T>
struct IsInt {
    static const bool value = false;
};
 
// 忘记特化,所有类型都得到 false

问题:模板元编程错误常发生在编译期,最好用 static_assert 立即验证。

注意事项

  • 模板元编程是 C++ 最不透明的部分之一,应该按需学习。
  • 理解类型萃取比背复杂技巧更重要。
  • 高级主题包括 SFINAE、检测惯用法、concepts、折叠表达式、模板递归、非类型模板参数。