记录 c++ 关于模板相关的要点。


1. 模板


1.1 资料

《C++ Templates (第2版·英文版)》: https://book.douban.com/subject/30226708/

《c++ Templates 第2版的中文翻译》:https://github.com/Walton1128/CPP-Templates-2nd–


1.2 模板特化 (template specialization)

或者叫模板特例化,是指为某个模板类或函数定义专门的实现,以处理特定类型参数的情况。

特化的本质是实例化一个模板。


1、函数模板特化

#include <iostream>

template<typename T>
void p(T t) {
    std::cout << "This is general type " << t << std::endl;
}

template<>
void p<int>(int i) {
    std::cout << "This is a int type " << i << std::endl;
}

int main() {
    p(10.0);
    p(20);
    return 0;
}

输出:

This is general type 10
This is a int type 20


2、类模板特化

写法与函数模板特化类似:

#include <iostream>

template<typename T1, typename T2>
class A {
    T1 t1;
    T2 t2;
public:
    A(T1 _t1, T2 _t2) : t1(_t1), t2(_t2) { std::cout << "This is a normal template " << t1 << " " << t2 << std::endl; }
};

template<>
class A<int, int> {
    int t1;
    int t2;
public:
    A(int _t1, int _t2) : t1(_t1), t2(_t2) { std::cout << "This is a specialization template " << t1 << " " << t2 << std::endl; }
};

int main() {
    A<char, int> a('a', 10);
    A<int, int> b(1, 2);
    return 0;
}

输出:

This is a normal template a 10
This is a specialization template 1 2

3、类模板只特例化成员

可以不特例化整个类,而特例化类中的某些成员,比如这样:

#include <iostream>
#include <string>

template<typename T>
class A {
    T t;
public:
    A(T _t) : t(_t) {}
    void p() { std::cout << "normal version of p : " << t << std::endl; }
};

template<>
void A<int>::p() {
    std::cout << "int version of p : " << t << std::endl;
}

int main() {
    A<std::string> a("hello");
    a.p();

    A<double> b(1.23);
    b.p();

    A<int> c(100);
    c.p();

    return 0;
}

输出:

normal version of p : hello
normal version of p : 1.23
int version of p : 100

只在模板参数为 int 的时候调用特别版本的函数 p


1.3 模板偏特化 (partial specialization)

模板偏特化是模板特化的一种特殊情况,也叫模板部分特化。是只对部分模板参数进行特化。


1、函数模板不支持偏特化

c++ 暂时不支持函数模板的偏特化。[1]

大部分情况下,可以用重载解决问题。

比如这样:

template<typename T, typename U>
void f(T t, U u);

// 重载
template<typename T>
void f(T t, int a);

关于为什么函数模板没有偏特化的,可以参考:

关于如何让函数模板支持类似偏特化的效果,可以参考:


2、类模板支持偏特化

例子:

#include <iostream>

template<typename T1, typename T2>
class A {
    T1 t1;
    T2 t2;
public:
    A(T1 _t1, T2 _t2) : t1(_t1), t2(_t2) { std::cout << "This is a normal template " << t1 << " " << t2 << std::endl; }
};

template<typename T2>
class A<int, T2> {
    int t1;
    T2 t2;
public:
    A(int _t1, T2 _t2) : t1(_t1), t2(_t2) { std::cout << "This is a partial template " << t1 << " " << t2 << std::endl; }
};

int main() {
    A<char, int> a('a', 10);
    A<int, int> b(1, 2);
    return 0;
}

输出:

This is a normal template a 10
This is a partial template 1 2

1.4 模板的匹配优先级

1、函数模板的优先级

1)首先,如果有非模板函数,则首选非模板函数;
2)其次,从未特化的模板 (base templates) 中选定最合适的一个
2.1)如果这模板刚好有一个特别匹配的特化了的函数模板,则选它
2.2)否则,以相应的参数实例化这个函数模板
3)找不到,报错

这其中第 2 点是最关键,也最容易搞错的,Herb Sutter 举了个例子来说明问题[5]:

template<class T>  // (a) 第一个 base template
void f( T );

template<>
void f<>(int *);   // (c) 模式 a 的全特化(full specialization 或 explicit specialization)

template<class T>  // (b) 第二个 base template,重载了 (a)
void f( T* );

// ...

int *p;
f ( p );           // 调用 (b)!因为重载决议会忽略特化版本,即(c)
                   // 所以先在(a)和(b)中选择,此时(b)刚好符合

他的解释是,特化的版本不会参与重载决议,只有 base template (即未特化) 参与重载决议,所以首先是从 2 个 base template 中选择最合适的,即(b)。

如果想要让你写的 “特化模板” 确保被使用,那就不要用模板,直接写一个非模板函数,它肯定会被确保首先使用。

对此,他给出了两个建议[5]:

1)不要写模板的特化版本,写一个非模板函数,即普通函数。
2)把函数包在 struct 里面,当成一个 static 函数,可以利用类的特化或偏特化能力,绕开函数模板的重载决议问题。


2、类模板的优先级

全特化版本 > 偏特化版本 > 正常版本


1.5 typename 与 class 关键字的区别

1、作为模板参数时,二者的含义是相当的。

template<typename T>template<class T> 是等价的。

2、typename 的额外用途

用于声明它后面的一串字符串是类型,而不是其他的。

比如这样:

#include <iostream>

class A {
    typedef int LType;
public:
    A() {
        A::LType a = 100;
        std::cout << a << std::endl;
    }
};

int main() {
    A();
    return 0;
}

像上面的代码,可能在有些编译器编译得过,有些编译不过。A::LType 是有歧义的,它可以指代 A 的下属类型,也可以指代 A 的静态变量。如果我们要明确的告诉编译器,A::LType 是一个类型,就需要这样写: typename A::LType a = 100;

类似的,经常在一些库里面看到函数的入参前加了 typename 关键字,它的作用就是表面后面跟着的是类型,而不是其他的。

另外,还有这样的写法:typedef typename std::vector<T>::size_type size_type;,这里的含义就是 typedef 定义了一个名为 size_type 的别名,它指代 typename std::vector<T>::size_typetypename 的作用就是声明 std::vector<T>::size_type 是类型,而不是其他的。


1.6 万能引用与完美转发

我记录在这篇文章了:《c++ 笔记六:c++11 的新特性》


2. 参考

[1] 五车书管. 函数模板的重载,偏特化,和全特化. Available at https://zhuanlan.zhihu.com/p/314340244, 2020-11-26.

[2] Herb Sutter. Why Not Specialize Function Templates. Available at http://www.gotw.ca/publications/mill17.htm, 2001-7.