Skip to content

模板元编程

模板 (template) 是 C++ 中用于泛型编程的机制,提供了一种“模式 (schema)”(或占位符 placeholder),用于表示实体的结构。模板是编译时功能,用于表示:

  • 函数族 (family of functions)
  • 类族 (family of classes)
  • C++14 开始的变量族 (family of variables)

模板允许程序员编写与数据类型无关的代码,从而提高代码的复用性和灵活性。使用模板可以定义函数模板、类模板以及变量模板,这些模板在编译时根据实际使用的数据类型自动实例化。

函数模板

函数模板 (function template) 是一种通用类型的函数架构,可以与多种数据类型一起工作,而无需为每种类型重复整个代码。

template<typename T> // 或 template<class T>
T add(T a, T b) {
    return a + b;
}

int c1 = add(3, 4); // c1 = 7
float c2 = add(3.0f, 4.0f); // c2 = 7.0f

模板实例化是用具体值或类型替换模板参数的过程。编译器会自动为每个模板实例生成一个函数实现。

template<typename T>
T add(T a, T b) {
    return a + b;
}

add(3, 4); // 生成:int add(int, int)
add(3.0f, 4.0f); // 生成:float add(float, float)
add(2, 6); // 已经生成
// 不生成其他实例
// 例如:char add(char, char) 尚未生成

C++ 模板实例化

模板实例化是指编译器根据模板参数生成具体代码的过程。在 C++ 中,模板实例化可以是隐式的也可以是显式的。

  • 隐式模板实例化

当编译器根据推断出的参数类型或明确的模板参数生成代码,并且只在定义被需要时进行,这称为隐式模板实例化。

template<typename T>
void f(T a) {}

void g() {
    f(3); // 隐式生成:void f(int)
    f<short>(3.0); // 隐式生成:void f(short), 这里的“隐式”指的是将 `double` 类型的 `3.0` 隐式转换为 `short` 类型的参数。你确实显式指定了模板参数类型 `T` 为 `short`,但对于 `3.0` (默认为 `double` 类型)到 `short` 的转换是在调用时隐式进行的。因此,这里的模板实例化依然被视为是由编译器自动处理的部分。
}

template void f<int>(int); // 显式生成:void f(int), 指示编译器在此处为特定类型 `int` 创建 `f()` 函数的实例
  • 显式模板实例化

显式模板实例化发生在编译器根据声明中指定的显式模板参数生成代码。这在处理多个翻译单元时非常有用,可以减少二进制大小。

template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // 隐式实例化
    auto result1 = max(10, 20); // 编译器自动推断为 max<int>(int, int)

    // 显式实例化
    auto result2 = max<double>(10.5, 20.5); // 明确指定模板参数为 double

    // 显式模板实例化声明
    template void max<double>(double, double); // 在全局或命名空间作用域内

    return 0;
}

模板参数

模板参数是在 template 关键字后定义的名称。

template<typename T>
void f() {}
f<int>(); // typename T 是模板参数,int 是模板参数实例

模板参数可以是泛型类型,例如 typenameclass,也可以是非类型模板参数(NTTP),如 intenum 等。

template<int A, int B>
int add_int() {
    return A + B; // 在编译时计算
} // 示例:add_int<3, 4>();

enum class Enum { Left, Right };
template<Enum Z>
int add_enum(int a, int b) {
    return (Z == Enum::Left) ? a + b : a;
} // 示例:add_enum<Enum::Left>(3, 4);
  • 带有默认值的模板参数(C++11)
    template<int A = 3, int B = 4>
    void print1() { cout << A << ", " << B; }
    print1<2, 5>(); // 打印 2, 5
    print1<2>(); // 打印 2, 4 (B 有默认值)
    print1<>(); // 打印 3, 4 (A, B 都有默认值)
    print1(); // 打印 3, 4 (A, B 都有默认值)
    

模板参数也可以没有名称,当不需要在模板定义中使用参数时可以省略名称。

template<typename = void>
void g() {}
g(); // 生成

C++11 允许模板参数被之前的值初始化:

template<int A, int B = A + 3>
void f() {
    cout << B;
}
f<3>(); // B 是 6

template<typename T, int S = sizeof(T)>
void g(T) {
    cout << S;
}
g(3); // S 是 4

模板函数重载与模板特化

模板函数不仅可以重载 (overloading),还可以进行模板特化 (specialization),以适应特定类型或参数组合的具体实现。

你可以根据参数数量或类型来重载模板函数。

template<typename T>
T add(T a, T b) {
    return a + b;
} // 示例:add(3, 4);

template<typename T>
T add(T a, T b, T c) {
    return a + b + c;
} // 示例:add(3, 4, 5);

template<int C, typename T>
T add(T a, T b) {
    return a + b + C;
} // "C" 是签名的一部分

模板特化是为特定的模板参数组合提供具体实现的机制。

template<typename T>
bool compare(T a, T b) {
    return a < b;
}

template<>
bool compare<float>(float a, float b) {
    return (fabs(a - b) < 0.0001); // 假设的实现,使用一定的误差范围
}

template<>
bool compare<float>(float a, float b) {
return ... // a better floating point implementation
}

模板参数类型

C++ 中的模板参数可以是多种类型,包括:

  • 整型(integral type)
  • 枚举类型(enum, enum class)
  • 浮点类型(floating-point type,需C++20支持)
  • auto占位符(auto placeholder,自C++17起支持)
  • 类字面量和概念(class literals and concepts,自C++20起支持)
  • 通用类型(generic type typename)

较少见的模板参数类型包括:

  • 函数
  • 引用/指向全局静态函数或对象的指针
  • 成员类型的指针
  • nullptr t(自C++14起支持)

  • 传入多个参数

    template<float V> // 只有 c++ 20 支持
    void print_float() {}
    
    template<typename T>
    void print() {
        cout << T::x << ", " << T::y;
    }
    
    struct Multi {
        static const int x = 1;
        static constexpr float y = 2.0f;
    };
    
    print<Multi>(); // 输出 "1, 2"
    
  • 数组和指针

template<int* ptr>
void g() {
    cout << ptr[0];
}

template<int (&array)[3]>
void f() {
    cout << array[0];
}

int array[] = {2, 3, 4}; // 全局变量

int main() {
    f<array>(); // 输出 2
    g<array>(); // 输出 2
}
  • 类成员作为模板参数

    struct A {
        int x = 5;
        int y[3] = {4, 2, 3};
    };
    
    template<int A::*x>
    void h1() {}
    
    template<int (A::*y)[3]>
    void h2() {}
    
    int main() {
        h1<&A::x>();
        h2<&A::y>();
    }
    
  • 函数作为模板参数

    template<int (*F)(int, int)>
    int apply1(int a, int b) {
        return F(a, b);
    }
    
    int f(int a, int b) { return a + b; }
    int g(int a, int b) { return a * b; }
    
    template<decltype(f) F>
    int apply2(int a, int b) {
        return F(a, b);
    }
    
    int main() {
        apply1<f>(2, 3); // 返回 5
        apply2<g>(2, 3); // 返回 6
    }
    

编译时工具 (compile-time unilities)

  • static_assert

static_assert 关键字用于编译时测试断言,如 sizeof、字面量、模板和 constexpr。如果 static_assert 的条件失败,则程序不会编译。

static_assert(2 + 2 == 4, "test1"); // 正常,会编译
static_assert(2 + 2 == 5, "test2"); // 编译错误,显示 "test2"

C++17 版本开始允许不带消息的断言:

template<typename T, typename R>
void f() { static_assert(sizeof(T) == sizeof(R)); }

f<int, unsigned>(); // 正常,会编译
// f<int, char>(); // 编译错误
  • using

using 关键字用于引入别名声明或别名模板,是 typedef 的增强版本,提供更可读的语法,并能与模板结合使用,适用于简化复杂的模板表达式,并允许为部分和完全特化引入新名称。

typedef int distance_t; // 等同于:
using distance_t = int;

typedef void (*function)(int, float); // 等同于:
using function = void (*)(int, float);
  • decltype decltype 关键字用于捕获实体或表达式的类型,总是在编译时求值,不执行任何运行时操作。
int x = 3;
int& y = x;
const int z = 4;
int array[2];
void f(int, float);

decltype(x) d1; // int
decltype(2 + 3.0) d2; // double
decltype(y) d3; // int&
decltype(z) d4; // const int
decltype(array) d5; // int[2]
decltype(f(1, 2.0f)) d6; // void
using function = decltype(f);

C++11 中使用 decltype 自动推导返回类型:

template<typename T, typename R>
decltype(T{} + R{}) add(T x, R y) {
    return x + y;
}
unsigned v1 = add(1, 2u);
double v2 = add(1.5, 2u);

C++14 引入更简洁的自动类型推导:

template<typename T, typename R>
auto add(T x, R y) {
    return x + y;
}

类型特征

自省是指能够检查一个类型并查询其属性的能力。反射是计算机程序检查、内省并修改其自身结构和行为的能力。C++通过类型特性(type traits)提供编译时反射和自省功能。

类型特性在C++11中引入,定义了一个编译时接口来查询或修改类型的属性。

template<typename T>
T integral_div(T a, T b) {
    return a / b;
}

integral_div(7, 2); // 返回 3 (int)
integral_div(7l, 2l); // 返回 3 (long int)
integral_div(7.0, 3.0); // 错误!浮点数不是整数类型
两种解决方案:(1)专门化(2)类型特性 + static assert
#include <type_traits>

template<typename T>
T integral_div(T a, T b) {
    static_assert(std::is_integral<T>::value, "integral_div accepts only integral types");
    return a / b;
}
  • 类型查询

    • is_integral 检查是否为整型(如 bool,char,unsigned char,int,long 等)
    • is_floating_point 检查是否为浮点型(如 float,double)
    • is_arithmetic 检查是否为整型或浮点型
    • is_signed 检查是否为有符号类型(如 float,int 等)
    • is_unsigned 检查是否为无符号类型(如 unsigned,bool 等)
    • is_enum 检查是否为枚举类型(如 enum,enum class)
    • is_void 检查是否为 void
    • is_pointer 检查是否为指针(如 T*)
    • is_null_pointer 检查是否为 nullptr(C++14)
  • 实体类型查询

    • is_reference 检查是否为引用(如 T&)
    • is_array 检查是否为数组(如 T (&)[N])
    • is_function 检查是否为函数类型
  • 类查询

    • is_class 检查是否为类类型(如 struct,class)
    • is_abstract 检查是否为具有至少一个纯虚函数的类
    • is_polymorphic 检查是否为具有至少一个虚函数的类
  • 类型属性查询

    • is_const 检查类型是否为 const
  • 类型关系

    • is_same<T, R> 检查 T 和 R 是否为同一类型
    • is_base_of<T, R> 检查 T 是否为 R 的基类
    • is_convertible<T, R> 检查 T 是否可转换为 R
  • 类型操作

    • make_signed 生成有符号类型
    • make_unsigned 生成无符号类型
    • remove_pointer 移除指针(如 T* → T)
    • remove_reference 移除引用(如 T& → T)
    • add_pointer 添加指针(如 T → T*)
    • add_lvalue_reference 添加引用(如 T → T&)
    • remove_const 移除 const(如 const T → T)
    • add_const 添加 const
    • common_type<T, R> 返回 T 和 R 之间的共同类型
    • conditional<pred, T, R> 如果 pred 为 true,返回 T,否则返回 R
    • decay<T> 返回作为函数参数按值传递时的同类型
#include <type_traits>

template<typename T>
void f(T ptr) {
    using R = std::remove_pointer_t<T>;
    R x = ptr[0]; // char
}

template<typename T>
void g(T x) {
    using R = std::add_const_t<T>;
    R y = 3;
    // y = 4; // 编译错误
}

char a[] = "abc";
f(a); // T: char*
g(3); // T: int

template<typename T, typename R>
std::common_type_t<R, T> add(T a, R b) {
    return a + b;
}

using result_t = decltype(add(3, 4.0f));
result_t x = add(3, 4.0f);

template<typename T, typename R>
auto f(T a, R b) {
    constexpr bool pred = sizeof(T) > sizeof(R);
    using S = std::conditional_t<pred, T, R>;
    return static_cast<S>(a) + static_cast<S>(b);
}

f(2, 'a'); // 返回 int
f(2, 2ull); // 返回 unsigned long long
f(2.0f, 2ull); // 返回 unsigned long long

类模板

类模板与函数模板类似,用于创建一系列类的模板

template<typename T>
struct A { // 类模板(类型名模板)
    T x = 0;
};

template<int N1>
struct B { // 类模板(数值模板)
    int N = N1;
};

A<int> a1; // a1.x 是 int x = 0
A<float> a2; // a2.x 是 float x = 0.0f
B<1> b1; // b1.N 是 1
B<2> b2; // b2.N 是 2

与模板函数不同的是,类可以部分特化。每次类的特化(无论是部分还是完全特化)都是一个全新的类,并不与通用类共享任何内容。

template<typename T, typename R>
struct A {}; // 通用类模板

template<typename T>
struct A<T, int> {}; // 部分特化

template<>
struct A<float, int> {}; // 完全特化

template<typename T, typename R>
struct A { // 通用类模板
    T x;
};

template<typename T>
struct A<T, int> { // 部分特化
    T y;
};

A<float, float> a1;
// a1.x; // 正确,使用通用模板
// a1.y; // 编译错误

A<float, int> a2;
// a2.y; // 正确,使用部分特化
// a2.x; // 编译错误

以下是使用类模板进行类型判断和值特化的示例:

template<typename T, typename R>
struct is_same {
    static constexpr bool value = false;
};

template<typename T>
struct is_same<T, T> { // 部分模板特化
    static constexpr bool value = true;
};

cout << is_same<int, char>::value; // 输出 false,使用通用模板
cout << is_same<float, float>::value; // 输出 true,使用部分模板
#include <type_traits>
template<typename T>
struct is_const_pointer : std::false_type {}; // 通用模板声明

template<typename R>
struct is_const_pointer<const R*> : std::true_type {}; // 部分特化

cout << is_const_pointer<int*>::value; // 输出 false,使用通用模板
cout << is_const_pointer<const int*>::value; // 输出 true,使用部分特化
cout << is_const_pointer<int* const>::value; // 输出 false,使用通用模板

类模板用于比较的示例:

#include <type_traits>
template<typename T>
struct A {};

template<typename T, typename R>
struct Compare : std::false_type {}; // 通用模板声明

template<typename T, typename R>
struct Compare<A<T>, A<R>> : std::true_type {}; // 部分特化

cout << Compare<int, float>::value; // 输出 false,使用通用模板
cout << Compare<A<int>, A<int>>::value; // 输出 true,使用部分特化
cout << Compare<A<int>, A<float>>::value; // 输出 true,使用部分特化