一、 模板实例化记要

1. 介绍

众所周知,模板分为 函数模板类模板

两者的主要差别是:

  • 函数模板定义的是函数和类方法,类模板定义的主要是 classstruct
  • 函数模板不能特例化,但能重载,类模板能特例化,但不能重复定义;
  • 函数模板能通过调用实参自动推导匹配类型,而类模板必须显性声明;
  1. 本文的实验环境是 RT-Thread Studio。
  2. 函数模板重载可以理解函数的重载,也可以理解为函数模板的重载,后面有解释。
  3. 类模板在同一个命名空间时,只能是唯一。

2. 函数模板的实例化

2.1 关于函数的重载

下面这段代码是用来测试 —— 函数被调用时,不同实参形式,到底会生成或匹配到哪些函数形式。其中注释部分分别代表几各种函数形式:

  • func(T)
  • func(T &)
  • func(T &&)
  • func(T *)
  • func(T const)
  • func(T volatile)
  • func(T const volatile)
//template <typename T>
//void func(T a) {rt_kprintf("func<T>()\n");}

//template <typename T>
//void func(T &a) {rt_kprintf("func<T &>()\n");}

//template <typename T>
//void func(T &&a) {rt_kprintf("func<T &&>()\n");}

//template <typename T>
//void func(T *a) {rt_kprintf("func<T *>()\n");}

//template <typename T>
//void func(T const a) {rt_kprintf("func<T const>()\n");}

//template <typename T>
//void func(T volatile a) {rt_kprintf("func<T volatile>()\n");}

//template <typename T>
//void func(T const volatile a) {rt_kprintf("func<T const volatile>()\n");}


int main(void)
{
    int a = 13;
    int &r = a;
    int *p = &a;
    const int ca = 13;
    volatile int va = 13;
    volatile const int cva = 13;

    func(a);
    func(r);
    func(13);
    func(p);
    func(ca);
    func(va);
    func(cva);

    return 0;
}

而测试的方法,除了观察 print 的内容外,还能查看调试窗口的 反汇编

image.png

如果没有这个窗口的话, 可以在此打开。

image.png

================ 总结测试的结果: ================

  1. func(T)func(T const)func(T volatile)func(T const volatile) 在编译器看来是同一类型。即任意两者或以上同时存在均报错 重复定义
    redefinition of 'template<class T> void func(T)'
    而其中访问修饰符或优化修饰符与实参同,换言之,你定义函数的形参无论是哪种 cv 形式(constvolatile),影响的都只是函数内部的作用域。但如果实参有要求,则形参 T 也自动推导为相同形式。

但请注意 引用版本 的不同,func(T &&)func( const T &&) 是可以共存的。

  1. 传值版本 func(T)引用版本func(T &)func(T &&)) 不能共存,
    call of overloaded 'func(int&)' is ambiguous
    因为调用 int a = 13; fun(a); 时,可理解为 复制值过去 ,也可理解为 传递引用过去 ,故存在歧义。

  2. func(T) 版本生成的实例,不管实参类型是 intint& 还是 int&&,均是传值版本,会产生复制操作。

  3. func(T &&)func(T &) 更具有适用意义。众所周知,C++ 是有左右值引用之分的。一般的理解是 T & 引用左值类型,T && 引用右值类型。所以,假如你的程序是想分别对左右值有不同的处理的话, 可以分别定义两个函数模板。但 如果你只定义一个引用函数模板的话,你就应定义 func(T &&) 。因此函数模板的引用存在折叠原则,具体网上可以查看。

  4. func(T *) 指针类型是比较特别的。上面说过,重载的版本中(形参数量相同的前提下),可只能定义 值版本T) 或 引用版本T&T&&) 两者中的一种。而对于 指针类型 而言,哪种都可以实例出来。
    值版本中,生成实例 T = <int *> ;而引用版本,则生成 T = <int *&>
    只有当你想单独区分传递是否为指针时,才定义 func(T *)

我画了一个可能不完全合理的适当范围图,

image.png

2.2 关于函数模板形式选择的若干建议

  1. 若你不想实参被修改,则定义 func(T)
  2. 若你想传递引用,则定义 func(T &&),使得传递字面量也适用;
  3. 若想单独区分左右值引用或指针,则分别定义相应版本;
  4. 若想传递引用但又不修改其内容,可只定义 func(const T &&)
  5. 若想区分可修改的引用或不可修改的引用,则要对两个版本分别定义;

2.3 关于函数模板不能特例化的说法的修改

不是说函数模板不能特例化,更确切的说法就是,函数模板不需要类模板特例化的形式

template <typename T>
void func(T a) {rt_kprintf("func(T a)\n");}

template <>
void func<double>(double a) {rt_kprintf("func(double a)\n");}

上面的是模仿类模板特例化形式写的,你会发现很鸡胁。函数重载不是形式更漂亮吗?!

void func(double a) {rt_kprintf("func(double a)\n");}

template <typename T>
void func(T a) {rt_kprintf("func(T a)\n");}

另外,在同一个 namespace 里,是允许多个同名函数存在的,只要参数类型匹配不相同就行。

void func(double a) {rt_kprintf("func(double a)\n");}

template <typename T>
void func(T a) {rt_kprintf("func(T a)\n");}

template <typename T, typename U>
void func(T a, U b) {rt_kprintf("func(T a, U b)\n");}

这更是类模板所不能达到的。故特例化只适合类模板。

3. 类模板实例化

3.1 修饰符的特例化

先上测试代码,

template <typename T>
class A {
public:
    A() {rt_kprintf("A<T>()\n");}
};

template <typename T>
class A<T*> {
public:
    A() {rt_kprintf("A<T*>()\n");}
};

template <typename T>
class A<T&> {
public:
    A() {rt_kprintf("A<T&>()\n");}
};

template <typename T>
class A<T&&> {
public:
    A() {rt_kprintf("A<T&&>()\n");}
};

template <typename T>
class A<T const> {
public:
    A() {rt_kprintf("A<T const>()\n");}
};

template <typename T>
class A<T volatile> {
public:
    A() {rt_kprintf("A<T volatile>()\n");}
};

template <typename T>
class A<T const volatile> {
public:
    A() {rt_kprintf("A<T cv>()\n");}
};


int main(void)
{
    A<int>                  a1;
    A<int &>                a2;
    A<int &&>               a3;
    A<int const>            a4;
    A<int volatile>         a5;
    A<int *>                a6;
    A<int const volatile>   a7;

    return 0;
}

类模板与函数模板不同,const TT 视为不同的匹配模式。

上例中,若实例化 <const int &> 类型的,则会自动匹配到 <T &> 。其中 T = const int

这个修饰符的匹配是 元编程 里很重要的一个环节。

3.2 类型匹配

template <typename T>
class B {
public:
    B() {rt_kprintf("B<T>()\n");}
};

template <>
class B<int> {
public:
    B() {rt_kprintf("B<int>()\n");}
};

template <>
class B<int *> {
public:
    B() {rt_kprintf("B<int *>()\n");}
};

template <>
class B<double> {
public:
    B() {rt_kprintf("B<double>()\n");}
};

template <>
class B<void> {
public:
    B() {rt_kprintf("B<void>()\n");}
};

template <>
class B<void *> {
public:
    B() {rt_kprintf("B<void *>()\n");}
};


int main(void)
{
    B<int>      b1;
    B<int *>    b2;
    B<double>   b3;
    B<void>     b4;
    B<void *>   b5;
    B<A<int>>   b6; // => B<T>

    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容