C++ 模板与泛型编程 理解

目录

[一、了解隐式接口和编译期多态(Effective C++ 41)](#一、了解隐式接口和编译期多态(Effective C++ 41))

编译期模板的典型场景:

[二、了解 typename 的双重意义(Effective C++ 42)](#二、了解 typename 的双重意义(Effective C++ 42))

什么时候必须写typename

什么时候不能加typename

[三、学习处理模板化基类内的名称(Effective C++ 43)](#三、学习处理模板化基类内的名称(Effective C++ 43))

编译器思考过程:

为什么非模板类不需要那么麻烦?

[四、将与参数无关的代码抽离 templates(Effective C++ 44)](#四、将与参数无关的代码抽离 templates(Effective C++ 44))

[五、运用成员函数模板接受所有兼容类型(Effective C++ 45)](#五、运用成员函数模板接受所有兼容类型(Effective C++ 45))

[六、需要类型转换时请为模板定义非成员函数(Effective C++ 46)](#六、需要类型转换时请为模板定义非成员函数(Effective C++ 46))

[七、请使用 traits classes 表现类型信息(Effective C++ 47)](#七、请使用 traits classes 表现类型信息(Effective C++ 47))

[八、认识 template 元编程(Effective C++ 48)](#八、认识 template 元编程(Effective C++ 48))

为什么要有这个东西?

TMP的核心机制

[现代 C++:TMP 已经全自动化了:](#现代 C++:TMP 已经全自动化了:)


一、了解隐式接口和编译期多态(Effective C++ 41)

C++还支持一种不依赖virtual的多态,它在编译期实现,且依靠隐式接口

类型 绑定时机 机制 示例 关键特征
运行期多态 运行时(dynamic binding) virtual 函数 + 继承 Shape* s = new Circle; s->draw(); 需要指针/引用,性能略低
编译期多态 编译时(static binding) 模板 (template) + 隐式接口 draw(c); 其中 cdraw() 函数 无 virtual 开销、更灵活

在OOP(面向对象编程)继承中,接口是显式声明的:

复制代码
class Shape {
public:
    virtual void draw() const = 0;   // 显式接口
};

所有继承 Shape 的类都必须实现 draw()

而在 C++ 模板世界里------

接口是隐式的

只要类"有某个成员函数 ",模板就可以用。

编译器在实例化时自动检查。

复制代码
#include <iostream>
using namespace std;

struct Circle {
    void draw() const { cout << "Draw circle\n"; }
};

struct Square {
    void draw() const { cout << "Draw square\n"; }
};

template <typename T>
void render(const T& shape) {
    shape.draw();   // 编译期多态:只要求 T 有 draw()
}

int main() {
    Circle c; Square s;
    render(c);
    render(s);
}
// 输出
Draw circle
Draw square

没有任何继承,也没有 virtual。但 render() 对不同类型对象表现出不同行为------这就是编译期多态

编译器只看:

"你传进来的 T 有没有这个函数名?"

这就是编译期多态

对比维度 显式接口(继承) 隐式接口(模板)
定义方式 声明 virtual 函数 只要存在可用函数
绑定时机 运行时 dynamic binding 编译时 static binding
类型要求 必须继承 Base 并 override 只需满足函数调用条件
调用开销 有 vtable 查找 无 vtable 开销
灵活性 稳定、结构清晰 更灵活、类型可混合
错误检查 运行时(常见逻辑错) 编译期(编译器报错)

编译期模板的典型场景:

1、模板函数

复制代码
template <typename T>
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

只要 T 支持赋值运算,编译器就能实例化。

2、模板类(泛型容器)

复制代码
template <typename T>
class Vector {
public:
    void push_back(const T& value);
};

T 只需要能被复制,不要求继承任何基类。

3、策略模式(Policy Based Design)

复制代码
template <typename SortingPolicy>
class Container : private SortingPolicy {
public:
    void sort() { this->sortImpl(); }
};

struct QuickSort { void sortImpl() { cout << "QuickSort\n"; } };
struct MergeSort { void sortImpl() { cout << "MergeSort\n"; } };

Container<QuickSort> a; a.sort(); // QuickSort
Container<MergeSort> b; b.sort(); // MergeSort

这就是典型的"编译期策略多态"。

特点 运行期多态 (virtual) 编译期多态 (template)
绑定时机 运行时 编译时
灵活性 支持基类指针统一操作 要求类型在编译时已知
性能 有 vtable 开销 无运行时开销
错误发现 运行时 / 逻辑错 编译时报错
可扩展性 新类型需继承基类 新类型只要满足接口形式
典型用途 插件系统、接口多态 泛型算法、STL

virtual 函数 → 显式接口 + 运行期多态
模板 → 隐式接口 + 编译期多态

前者结构化、稳定;

后者灵活、高效

二、了解 typename 的双重意义(Effective C++ 42)

两种意义

用法类别 意义 示例 说明
声明模板参数时 表示"类型参数" template <typename T> ✅ 等价于 template <class T>
在模板定义内部使用时 告诉编译器 "这是一个类型名" typename T::value_type ❗ 这时必须写 typename,否则编译不过

1.模板参数

复制代码
template <typename T>
void func(T param);
//等于
template <class T>
void func(T param);

这里 typename 只是告诉编译器:

"T 是一个类型参数"。

在模板参数列表里,typenameclass 没有区别。

2.在模板内部的"嵌套依赖类型"

复制代码
//错误写法
template <typename T>
void printFirst(const T& container) {
    T::const_iterator iter = container.begin(); // ❌ 编译错误
    std::cout << *iter << std::endl;
}
//报错:
error: need 'typename' before 'T::const_iterator' because 'T' is a dependent scope

在模板定义阶段,编译器看到T::const_iterator,它不知道

"T::const_iterator 是一个类型?还是一个静态成员变量?还是别的符号?"

此时 T 是一个 dependent name (依赖于模板参数),

C++ 语法要求你必须显式告诉编译器:

"放心,这是一个类型名。"

复制代码
//正确写法:
template <typename T>
void printFirst(const T& container) {
    typename T::const_iterator iter = container.begin();  // ✅ OK
    std::cout << *iter << std::endl;
}

什么时候必须写typename

当在模板里使用依赖于模板参数的嵌套类型时,必须加typename

即:

  • T::iterator

  • U::value_type

  • MyType::NestedType

语句 是否需要 typename 解释
T::iterator it; ✅ 需要 T 未知,依赖模板参数
typename T::iterator it; ✅ 正确
MyClass::iterator it; ❌ 不需要 已知类型,不依赖模板参数
template <typename T> class A {}; ✅ 必须 声明模板参数
template <class T> class A {}; ✅ 同等合法

什么时候不能加typename

1、在模板参数声明里

复制代码
template <typename T>   // ✅ OK
class Container {};

2、在非类型依赖的名字前

复制代码
class Foo {
public:
    using value_type = int;
};

template <typename T>
void f() {
    Foo::value_type x;  // ❌ 不能写 typename Foo::value_type
}

总结

1.在模板参数声明中:

→ 表示「这是一个类型参数」

(等价于 class

2.在模板定义内部:

→ 表示「这是一个依赖于模板参数的嵌套类型」

(告诉编译器:T::xxx 是类型,不是变量)

三、学习处理模板化基类内的名称(Effective C++ 43)

当你的派生类是模板类,而它继承子一个模板基类时,派生类内部访问基类的成员时,编译器可能找不到那些名字。

复制代码
#include <iostream>
using namespace std;

template <typename T>
class Base {
protected:
    void printBase() { cout << "Base::printBase()" << endl; }
    int baseValue = 10;
};

template <typename T>
class Derived : public Base<T> {
public:
    void func() {
        printBase();     // ❌ 错误:编译器说找不到 printBase()
        cout << baseValue << endl;  // ❌ 同样报错
    }
};

编译错误:

复制代码
error: 'printBase' was not declared in this scope
error: 'baseValue' was not declared in this scope

derived继承了base但是访问不到base的函数,原因是因为c++编译器在模板定义阶段不会"假设"

任何模板参数有关的名字。

编译器思考过程:

1.当编译器定义Derived<T>时,看到

复制代码
class Derived : public Base<T> {};

此时它知道:

  • Base 是一个模板;

  • T 是个未知类型;

  • Base<T> 的内容取决于 T。

2.当它看到 printBase();

他会问:这个名字的作用域在哪里

编译器查找顺序:

1.当前类(Derived<T>)的作用域;

2.外层作用域(命名空间、全局)

3.不会自动去查基类模板的作用域!(因为基类依赖模板参数)

我现在还不知道 Base<T> 里到底有没有 printBase(),所以我暂时不敢去那查。

结果:编译器不敢假设基类模板的内容,于是报告"找不到符号"

解决方案:

1.加上this->

告诉编译器,"这个成员来自当前对象(可能在基类里)"

复制代码
template <typename T>
class Derived : public Base<T> {
public:
    void func() {
        this->printBase();       // ✅ OK
        cout << this->baseValue; // ✅ OK
    }
};

啊,你写了this->printBase,那就是说这个名字是"当前对象"的成员咯?

当前对象是谁嘞?是 Devived<T>的实例,而Derived<T>继承了Base<T>。

所以我知道了,

我要去当前对象(this)的所有成员里找

包括它的基类成员

虽然Base<T>的定义要等实例化时才确定 ,但我知道"到时候可以去查"。

好的!那我可以把它延迟到实例化时解析!

2.用作用域限定Base<T>::

复制代码
template <typename T>
class Derived : public Base<T> {
public:
    void func() {
        Base<T>::printBase();       // ✅ OK
        cout << Base<T>::baseValue; // ✅ OK
    }
};

直接告诉编译器"去 Base<T> 找这个名字"。

缺点是:如果 Base<T> 名字很长,这种写法比较繁琐。

3.用 using 声明引入名字

复制代码
template <typename T>
class Derived : public Base<T> {
public:
    using Base<T>::printBase;
    using Base<T>::baseValue;

    void func() {
        printBase();      // ✅ OK
        cout << baseValue << endl;  // ✅ OK
    }
};

把基类的名字显式"导入"到当前作用域中。

这是最干净的做法,也最推荐在大型项目中使用。

写法对比:

写法 可读性 适用场景 原理
this->func() 简短、常用 成员函数调用 显式告诉编译器"这是对象成员"
Base<T>::func() 明确、繁琐 静态成员或不在 this 上下文 显式指定查找路径
using Base<T>::func; 最清晰 多个成员导入 把名字引入当前作用域

为什么非模板类不需要那么麻烦?

这时候编译器知道Base的结构是固定的,不依赖模板参数,所以它能直接在基类作用域查找。

模板类不同,因为Base<T>在每个T下都不同。编译器在定义阶段下不能确定基类是否该有名字,必须要手动"引导查找"

四、将与参数无关的代码抽离 templates(Effective C++ 44)

模板会被编译器复制多份,如果某段代码与模板参数无关,就会被重复复制多次--浪费时间又浪费时间,所以要把它抽出去(放到非模板的地方)。

简单来说就是:

模板里值放"跟类型T真正相关的东西"。不相关的东西别放进去,会被复制好几份。

例子1:

复制代码
template<typename T>
void foo(const T& x) {
    std::cout << "Hello\n";  // ❌ 和 T 完全无关
    std::cout << x << "\n";  // ✅ 和 T 有关
}
//如果调用
foo(10);       // T = int  → 生成一份代码
foo(3.14);     // T = double → 再生成一份代码
foo("test");   // T = const char* → 再生成一份

那一行 "Hello" 被重复生成三次。

如果你 foo<T> 实例化 50 次,那段代码就重复 50 次

(占50倍空间、编译 50 次)。

如果代码是几百行,那就炸了。

解决:

cpp 复制代码
void printHello() {
    std::cout << "Hello\n";  // 只生成一次
}

template<typename T>
void foo(const T& x) {
    printHello();     // 调用即可,不重复生成
    std::cout << x << "\n";
}

现在 printHello():

  • 不管 foo<int>

  • foo<double>

  • foo<string>

它都只存在一份

例子2:类模板

模板类的每个成员函数都属于类模板本身。

类模板实例化=生成一个完整类,包括它的所有成员函数。

错误:

cpp 复制代码
template<typename T>
class Parser {
public:
    void parse(const T& t) {
        checkHeader();      // ❌ 这个函数根本与 T 无关,每次都会复制多份
        parseImpl(t);       // 只有这里才和 T 有关
    }

private:
    void checkHeader() {      // ❌ 会被重复 N 份
        std::cout << "Checking\n";
    }

    void parseImpl(const T& t) { ... } // 和 T 有关
};

正确:

cpp 复制代码
class ParserBase {
protected:
    void checkHeader() {
        std::cout << "Checking\n";   // 只存在 1 份!
    }
};

template<typename T>
class Parser : private ParserBase {
private:
    void parseImpl(const T&);
public:
    void parse(const T& t) {
        checkHeader();  // 不重复
        parseImpl(t);   // 和T有关
    }
};

五、运用成员函数模板接受所有兼容类型(Effective C++ 45)

如果希望一个类能接受"所有可以转换成它的类型",那么你应该写"模板化的成员函数",而不是普通函数。

尤其是:模板复制构造函数,模板赋值运算符。

比如:

cpp 复制代码
class SmartPtr {
public:
    SmartPtr(SomeClass* ptr) : p(ptr) {}
private:
    SomeClass* p;
};

个类只能接受:

SmartPtr sp(new SomeClass);

但如果你有一个派生类:

class Derived : public SomeClass {};

你想这样写:

SmartPtr sp(new Derived); // ❌ 不能自动转换

C++ 不会把 Derived* 自动转为 SomeClass*

(因为构造函数不是模板)。

使用成员函数模板就能解决:

cpp 复制代码
class SmartPtr {
public:
    template<typename T>
    SmartPtr(T* ptr) : p(ptr) {}   // ❤️ 接受所有可以转为 SomeClass* 的类型
private:
    SomeClass* p;
};
cpp 复制代码
SmartPtr sp1(new SomeClass);  // OK
SmartPtr sp2(new Derived);    // OK!转换到 SomeClass*

这就是"成员函数模板接受兼容类型"。

cpp 复制代码
std::shared_ptr<Base> pb = std::make_shared<Derived>();

之所以可以接受derived,是因为它内部写了

cpp 复制代码
template <class Y>
shared_ptr(const shared_ptr<Y>& r);

而不是:

cpp 复制代码
shared_ptr(const shared_ptr& r);   // ❌ 只能接受同类型

注意:

虚函数不能是模板(c++不允许),所以这条主要用于构造/赋值。

本条的真正价值:

1.写更通用的类(智能指针,容器)、

2.无需繁琐写多个构造函数

cpp 复制代码
BasePtr(Derived* p) {}
BasePtr(Derived2* p) {}
BasePtr(Derived3* p) {}
//变成
template<typename T>
BasePtr(T* p) {}

3.避免不必要的强转

完整示例:

cpp 复制代码
template<typename T>
class SmartPtr {
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other) : ptr(other.ptr) {}

    T* ptr;
};
cpp 复制代码
SmartPtr<Base> pb;
SmartPtr<Derived> pd;

pb = pd;     // ❤️ 自动兼容

这里有两个模板,但不是"套模板",而是"模板里的成员函数本身也是模板"。

总结:

如果你的类希望能接受"任何可以转为它的类型",
最好的方法就是:写"成员函数模板"。

比如:

  • 模板化构造函数

  • 模板化复制构造

  • 模板化赋值运算符

  • 模板化转换

这是写通用库(包括 STL、智能指针、policy-based design)的基础技巧。

六、需要类型转换时请为模板定义非成员函数(Effective C++ 46)

如果你写一个模板类,并且这个类需要支持隐式类型转换(例如用于运算符重载),那么相关的运算符函数应该写成"非成员函数(friend或外部函数)",而不是成员函数。(看不懂啊看不懂)

原因:

成员函数不支持两边的隐式转换,而非成员函数可以。

成员函数 operator*

只能让 右边 的操作数发生隐式类型转换。

但不能让左边发生转换 ,因为左边被绑定成了 this,必须已经是类类型。

cpp 复制代码
template<typename T>
class Rational {
public:
    Rational(const T& num, const T& den) : n(num), d(den) {}

    // 成员版本的 operator*
    Rational operator*(const Rational& rhs) const {
        return Rational(n * rhs.n, d * rhs.d);
    }

private:
    T n, d;
};

普及常识:

函数名 重载的是
operator+ 加法
operator- 减法
operator* 乘法
operator/ 除法
operator[] 下标运算符
operator() 函数调用运算符
operator-> 成员访问运算符
operator== 相等比较

现在做一个非常正常的操作:

cpp 复制代码
Rational<int> r(1, 2);
auto x = r * 2;     // ❌ 不工作

因为 operator* 是"成员函数":

cpp 复制代码
r.operator*(2);

而它的参数要求是 Rational<int>
2 并不会自动变成 Rational<int>

所以成员函数不支持:

右侧参数的隐式转换

你只允许:

cpp 复制代码
r * Rational<int>(2);
复制代码
这样就太难用了

换成非成员函数就可以:

cpp 复制代码
template<typename T>
class Rational {
    // ...

    // 让它成为友元,便于访问 n 和 d
    template<typename U>
    friend Rational<U> operator*(const Rational<U>& lhs, const Rational<U>& rhs);
};
template<typename U>
Rational<U> operator*(const Rational<U>& lhs, const Rational<U>& rhs) {
    return Rational<U>(lhs.n * rhs.n, lhs.d * rhs.d);
}

就可以:

cpp 复制代码
Rational<int> r(1, 2);

auto a = r * 2;  // ❤️ 自动把 2 转换成 Rational<int>
auto b = 2 * r;  // ❤️ 左边也可以自动转换!

这就是 Scott Meyers 强调的:"当你需要类型转换能力,以非成员函数实现。"'

完整正确写法:

cpp 复制代码
template<typename T>
class Rational {
public:
    Rational(const T& n = 0, const T& d = 1) : num(n), den(d) {}

    template<typename U>
    friend Rational<U> operator*(const Rational<U>& lhs,
                                 const Rational<U>& rhs);

private:
    T num, den;
};

template<typename U>
Rational<U> operator*(const Rational<U>& lhs, const Rational<U>& rhs) {
    return Rational<U>(lhs.num * rhs.num, lhs.den * rhs.den);
}

现在所有组合都支持:

cpp 复制代码
Rational<int> r(1, 3);

r * 5;       // OK
5 * r;       // OK
r * r;       // OK

总结:

当模板类需要隐式类型转换(尤其是运算符重载)时,

运算符应该写成"非成员函数模板",而不是成员函数。

七、请使用 traits classes 表现类型信息(Effective C++ 47)

这条是模板里最重要的一条。

traits class 是一个模板结构,用来"在编译期告诉你某个类型的特性"

换句话说:

我怎么让模板知道T是不是指针?是不是迭代器?他的value_type是啥?T能不能加法?T是所有符号的?

这些信息都不能在T本身写代码解决,但可以用traits!

cpp 复制代码
template<typename T>
void process(const T& x) {
    if ( ??? ) {          // 如果 T 是指针怎么办?
        // 做指针特化逻辑
    } else {
        // 做一般类型逻辑
    }
}

问题来了:

模板里你没办法写"if (T is pointer)"

因为 T 只是一个类型,不是值!

你无法直接比较类型,

但你需要一种机制:
让模板根据 T 的"类型特征"选择不同逻辑。

这就是 traits class 的使命。

traits的基本格式:

cpp 复制代码
template<typename T>
struct TypeTraits {
    static const bool isPointer = false;
};

// 针对指针模板特化
template<typename U>
struct TypeTraits<U*> {
    static const bool isPointer = true;
};

第一个模板:泛化版本

给所有类型的默认值,任何类型如果没有特殊处理,都认为不是指针

举例:

  • int → isPointer = false

  • double → isPointer = false

  • std::string → isPointer = false

第二个模板:偏特化版本

专门处理指针类型,当T是指针,这个特化就匹配。

  • int* → 匹配 TypeTraits<U*> → isPointer = true

  • double* → 匹配 TypeTraits<U*> → isPointer = true

  • MyClass* → 匹配 TypeTraits<U*> → isPointer = true

现在我们可以写:

cpp 复制代码
template<typename T>
void process(const T& x) {
    if (TypeTraits<T>::isPointer) {
        std::cout << "T 是指针类型\n";
    } else {
        std::cout << "T 是非指针类型\n";
    }
}

使用

cpp 复制代码
int x = 10;
int* p = &x;

process(x);  // → T 是非指针类型
process(p);  // → T 是指针类型

STL里最典型的traits:iterator_traits(迭代器特性)

cpp 复制代码
template<typename Iter>
void advance(Iter& it, int n) {
    if (??? it 是否是 random_access_iterator ???)
        it += n;   // O(1)
    else
        while (n--) ++it;  // O(n)
}

你怎么知道it哪种迭代器?

STL 用 traits:

cpp 复制代码
template<typename Iter>
struct iterator_traits {
    typedef typename Iter::iterator_category iterator_category;
};

然后可以这么写:

cpp 复制代码
template<typename Iter>
void advance(Iter& it, int n) {
    typedef typename iterator_traits<Iter>::iterator_category Cat;

    advanceImpl(it, n, Cat()); // 根据类型不同选不同函数
}

typedef: 给类型名起名

typename:告诉编译器,这是类型,不是成员变量。

typename Iter::iterator_category:找到Iter这个类型内部名为iterator_category的类型

比如 vector 的迭代器有:

cpp 复制代码
struct __vector_iterator {
    using iterator_category = random_access_iterator_tag;
};

typedef ... iterator_category:给这个类型重新起一个短名字叫 iterator_category。

然后就可以这么写:

cpp 复制代码
template<typename Iter>
void advance(Iter& it, int n) {
    typedef typename iterator_traits<Iter>::iterator_category Cat;

    advanceImpl(it, n, Cat()); // 根据类型不同选不同函数
}

最终实现智能调度:

cpp 复制代码
template<typename Iter>
void advanceImpl(Iter& it, int n, random_access_iterator_tag) {
    it += n;  // 快
}

template<typename Iter>
void advanceImpl(Iter& it, int n, input_iterator_tag) {
    while (n--) ++it;  // 慢
}

完整例子:

cpp 复制代码
#include <iostream>
using namespace std;

struct MyIter {
    using iterator_category = int; // 这是一个类型(别管具体是什么)
};

template<typename Iter>
struct iterator_traits {
    typedef typename Iter::iterator_category iterator_category;
};

int main() {
    // 等价于 int x; 因为 MyIter::iterator_category = int
    iterator_traits<MyIter>::iterator_category x = 123;

    cout << x << endl;
}

总结:

模板本身不知道类型信息,但 traits 能告诉模板"这个类型的特性"。

traits 是泛型编程的基础,可以让你做类型判断、类型选择、类型属性提取。

八、认识 template 元编程(Effective C++ 48)

模板元编程(Template Metaprogramming, TMP)是:让C++的模板系统在"编译期"执行"计算或逻辑",然后把结果用于生成最终的代码。

换句话说:

平时我们的代码是运行时执行

TMP的代码时编译器在编译时执行

你写的模板代码,在编译期间先跑一遍,产出结果,再用这些结果生成最终的可执行程序。

所以:模板元编程≈编译器程序语言

为什么要有这个东西?

因为它可以做三件普通 C++ 做不到的事情:

  1. 编译期计算 → 性能极高

例如常数折叠、编译期生成查表、提前优化。

  1. 编译期分派(dispatch)→ 更快,而不是 runtime(运行时) 判断

tips:dispatch="决定调用哪个函数" 的过程。

例如:

cpp 复制代码
if (iterator 是 random access) 用 +=
else 用 ++

这些逻辑如果运行时判断会慢,

TMP 让你在编译期"算出应该走哪个逻辑"。

  1. 自动生成大量重复代码(泛型)

比如 STL 之类的库,靠 TMP 自动生成无数模板类型。

最简单的例子:

cpp 复制代码
template<int n>
struct Factorial {
    static const int value = n * Factorial<n-1>::value;
};

template<>
struct Factorial<1> {
    static const int value = 1;
};

int x = Factorial<5>::value;  //  5! = 120,编译期就算好了

你没运行程序,它就算出来了。

这是最经典的 TMP pattern。

实际例子(其实和之前的例子是一样的):

cpp 复制代码
template<typename T>
struct IsPointer {
    static const bool value = false;
};

template<typename U>
struct IsPointer<U*> {
    static const bool value = true;
};
cpp 复制代码
std::cout << IsPointer<int>::value << std::endl;    // 0
std::cout << IsPointer<int*>::value << std::endl;   // 1

这个判断是编译期完成的!

实际用途:

应用 说明
类型判断 is_pointer, is_const, is_same
条件编译 enable_if、conditional
选择不同算法 random access vs input iterator
自动推断类型 iterator_traits
生成更高性能代码 loop unrolling、constexpr
编写泛型库 STL、Boost、Eigen 全在用

TMP的核心机制

1.模板的递归实例化能力

模板可以根据类型或整数无限递归生成新模型:

cpp 复制代码
template<int N> class A {
    A<N-1> next;   // 递归生成类型链
};

2.编译器常量

TMP 必须依赖编译期常数

cpp 复制代码
static const int value = n * Factorial<n-1>::value;

比如:

cpp 复制代码
template<int N>
struct A {};
// 不能写
int x = 10;
A<x> a;  // ❌ 不行

因为 x 只有运行期才有值。

因为 x 是一个 运行期变量

  • 它存储在栈里

  • 程序运行后才初始化

  • 编译器只知道"有个变量 x",不知道它的值是多少

3.模板特化(specialization)

TMP 的"if / else"就是靠特化实现:

cpp 复制代码
template<int N> struct Factorial { ... };  // 一般情况

template<> struct Factorial<1> { ... };    // 特殊情况

现代 C++:TMP 已经全自动化了:

在 C++11/14/17 之后,几乎不需要写递归 TMP。

因为标准库已经提供大量 TMP 工具:

  • std::conditional

  • std::enable_if

  • std::integral_constant

  • std::true_type / false_type

  • std::is_pointer

  • std::is_same

  • std::remove_reference

  • std::declval

  • std::is_convertible

  • std::is_trivial

只需要使用即可。

现代 TMP 还加入了:

  • constexpr if

  • constexpr function

  • concepts

  • requires

已经让 TMP 简洁很多。

总结:

模板元编程 = 利用模板系统在编译时做计算与逻辑,从而选择不同代码或优化执行效率。

TMP 的基础是:模板递归 + 特化 + 编译期常量。

只要理解:

  • traits 是 TMP 的应用

  • iterator dispatch 是 TMP

  • is_pointer / is_same 是 TMP

  • SFINAE、enable_if 是 TMP

就已经掌握了 99% 的 TMP。

相关推荐
醇氧2 小时前
【Windows】优雅启动:解析一个 Java 服务的后台启动脚本
java·开发语言·windows
hetao17338373 小时前
2025-12-12~14 hetao1733837的刷题笔记
数据结构·c++·笔记·算法
椰子今天很可爱3 小时前
五种I/O模型与多路转接
linux·c语言·c++
MapGIS技术支持3 小时前
MapGIS Objects Java计算一个三维点到平面的距离
java·开发语言·平面·制图·mapgis
程序员zgh3 小时前
C++ 互斥锁、读写锁、原子操作、条件变量
c语言·开发语言·jvm·c++
小灰灰搞电子4 小时前
Qt 重写QRadioButton实现动态radioButton源码分享
开发语言·qt·命令模式
by__csdn4 小时前
Vue3 setup()函数终极攻略:从入门到精通
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript
喵了meme4 小时前
C语言实战5
c语言·开发语言
廋到被风吹走4 小时前
【Java】常用设计模式及应用场景详解
java·开发语言·设计模式
Sammyyyyy4 小时前
DeepSeek v3.2 正式发布,对标 GPT-5
开发语言·人工智能·gpt·算法·servbay