关于模板,至少我们要先了解几个概念
一:函数模板
二:类模板
三:模板特化
四:形参参数包
模板覆盖的东西太多 我目前也不了解太多
函数模板 语法
template<typename 类型名,typename 类型名,typename ...多参包类型名>
//内部的typename可写多个 有时我们可能会看到 这里会写 class 意思大概是差不多的
返回值 函数名(){};
如
template<typename arg, typename ...args>
void set(arg s, args... d)
{
val = s;
base::set(d...);
};
这里我们可以看到 template<typename arg, typename ...args>
模板形参类型 有 arg 和 args 形参包
arg 自然就代表可接受一个参数类型,
而args 形参包也叫变参参数包 可接受多个参数
当然也有要求
一:
模板形参必须可推导,显示实例化也属于可推导
二:
有显示实例化当然也有隐式实例化
但隐式实例化有个要求
函数参数中必须可推导模板形参
如
template<typename arg, typename ...args>
void set(arg s, args... d)
{
val = s;
};
模板形参在函数参数中直接用到了
可如此调用 这叫隐式实例化
set(10,'g',50,100); //arg = int args = {char,int,int}
这叫显示实例化
set<int,char,int,int>(10,'g',50,100); //arg = int args = {char,int,int}
你会说 我们要是在函数参数中没一一使用到 模板形参怎么办?
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
自然就要显示实例化了 注意必须可推导
set<obj_2>(10, 100);
// Obj = obj_2 agr = int args ={int}
看就显示实例化了没用到的 Obj 模板形参 我们人看上去也是可推导的
编译器是人写的所以逻辑也是有些符合我们的思维的
函数特化有两种
偏特化(部分特化)
全特化
说实在的我也不太懂 我只能把我懂的部分 说一下
首先我们先看看 函数模板的偏特化 与 全特化
//主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
//特化版本 args* 形参包中 全为 指针类型
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{
Obj c(d..., s);
}
//特化版本 Ret(*s)(a...) 非成员函数的函数指针
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{
Obj c((*d)..., s());
}
//全特化
template<>
void set<obj_2,float,float>(float v, float d)
{
obj_2 c(d, v);
}
//全特化
template<>
void set<obj_2>(float v, float d,char k)
{
obj_2 c(d, v);
}
这里有个叫主模版的函数模板 用特化就必须得需要主模版 什么叫主模板?
我的理解中是 函数模板 最基本的哪个
比如上文
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
这个模板可以容纳下面几个特化出来的函数参数的样子
比如指针类型 非成员函数的函数指针类型 成员函数的函数指针类型
也就是说它更加全面
你说都更加全面了 我为什么还有特化
这是为了 处理不同的情况嘛 就和函数重载时 处理不同情况一样
比如
int geti()
{
return 50;
}
int b = 10, b2 = 100;
set<obj_2>(&b,&b2);
/*
调用的特化版本是
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{
Obj c(d..., s);
}
*/
set<obj_2>(&geti, &b2);
/*
调用的特化版本是
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{
Obj c((*d)..., s());
}
*/
set<obj_1>(0.5f, 5.3f);
/*
调用主模板版本
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
*/
set<obj_2>(0.5f, 5.3f,'p');
/*
调用的特化版本是
template<>
void set<obj_2>(float v, float d,char k)
{
obj_2 c(d, v);
}
*/
set<obj_2>(0.f);
/*
调用主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
*/
有人可能看到 有些特化怎么 模板形参比主模版还多
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{
Obj c((*d)..., s());
}
这就是模板特化的一部分特性 注意 特化版本的模板形参与主模版的模板形参并无瓜葛
就算他们是一样的名字
但是实则是有一定要求的
比如
//主模板
template<typename Obj, typename arg>
void set1(arg c) { Obj c{}; };
//全特化
template<>
void set1<obj_2,float>(float c) { obj_2 bc{}; };
//错误特化版本
template<>
void set1<obj_2,int,char>(int c) {};
/*
这里我们注意到了, set1<obj_2,int,char> 有三个模板实参
而 我们的主模板只需要 两个模板实参
这就是要求:
不能大于主模版要求的模板形参数目
*/
//错误特化版本 与上述一样
template<>
void set1<obj_2>(int c,char b) {};
和函数重载 很类似的规则
有人会问了 那你第一种怎么可写好多个模板实参?
注意主模版哦
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
Obj c(s,d...);
};
一眼上去三个模板形参 但是我们最后是个模板形参包啊
不限个数的啊 超过两个的形参 统统进入形参包
这里可能有少年提出这样的写法
template<typename arg, typename ...args>
void set<obj_2>(arg s, args... d)
{
obj_2 c(s,d...);
};
看着 嗯..... 我就特化处理 这个obj_2类型的
不过可惜 这样写法是错误的
注意右边的 编译输出错误
好了函数模板的特化说完了
---------------------------------------------------
现在我们来看看类模板
上模板
//主模版
template<typename tp>
struct t1
{
};
//特化版本
template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{
using F = Ret(Clss::*)(Args...);
t1(F fptr):ptr(fptr) {};
F ptr;
};
int main()
{
t1<decltype(&obj_2::gets)> b(&obj_2::gets);
return 0;
}
和函数模板特化差不多
类模板可通过构造函数的参数推断模板形参
template<typename tp>
struct t1
{
using type = tp;
t1(tp p) :tpo(p) {};
tp tpo;
};
template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{
using F = Ret(Clss::*)(Args...);
t1(F fptr):ptr(fptr) {};
F ptr;
};
int main()
{
t1<decltype(&obj_2::gets)> b(&obj_2::gets);
t1 b = t1(&obj_2::gets);//特化版本 struct t1 <Ret(Clss::*)(Args...)>
// F = int(obj_2::*)(int,int,char);
t1(obj_2()); // 推断的是主模版 tp = obj_2
return 0;
}
类模板可以弥补我们之前函数模板的遗憾
template< typename tp>
struct t2<obj_2,tp>
{
t2(tp p):op(p) {}
tp op;
obj_2 d;
};
它可以这样特化
但是又有可惜的事情了
t1 b3 = t1(obj_2()); //ok
auto c = t2<obj_2,decltype(b3)>(b3); //ok template< typename tp> struct t2<obj_2,tp>
auto c2 = t2<obj_2>(b3); // error 可惜不可以这样调用 至少我是能看出来 应该可以推导
template<typename obj,typename tp>
struct t2
{
using type = tp;
t2(obj* oj,tp p) :ptr(oj), tpo(p) {};
obj* ptr;
tp tpo;
};
//main 中
t1 b = t1(&obj_2::gets);
t1 b3 = t1(obj_2());
auto c3 = t2(&b, &b3);//可以
对了 忘记说一件事了
形参包 我们通过特化给它拆开
//主模版
template<typename ...T>
struct Tuple_text {};
//特化
template<>
struct Tuple_text<>
{
void set() {};
};
//特化
template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...> {
Ty1 val;
using base = Tuple_text<Ty2...>;
Tuple_text() {}
template<typename arg, typename ...args>
Tuple_text(arg a, args... d) :val(a), base(d ...) {}
template<typename ...arg>
void set(arg... args) {};
template<>
void set<>() {};
template<typename arg, typename ...args>
void set(arg s, args... d)
{
val = s;
base::set(d...);
};
base& get()
{
return *this;
}
};
这是今天学习到的 c++元组的类似做法
这里最关键的地方就是
template<>
struct Tuple_text<>
{
void set() {};
};
template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...>
{
Ty1 val;
using base = Tuple_text<Ty2...>;
Tuple_text() {}
template<typename arg, typename ...args>
Tuple_text(arg a, args... d) :val(a), base(d ...) {}
}
没想到吧 我们的构造函数都能模板
这个有点复杂
我们展开看看 C++ Insights (cppinsights.io) 这个网站可以展开模板
#include <cstdio>
//主模板
template<typename ... T>
struct Tuple_text
{
};
//特化
template<>
struct Tuple_text<>
{
inline void set()
{
}
};
//以下都为特化 实例化后其实也就是特化
template<>
struct Tuple_text<long> : public Tuple_text<>
{
long val;
using base = Tuple_text<>;
inline Tuple_text();
template<>
inline Tuple_text<long>(long a)
: Tuple_text<>()
, val{a}
{
}
};
template<>
struct Tuple_text<float, long> : public Tuple_text<long>
{
float val;
using base = Tuple_text<long>;
inline Tuple_text();
template<>
inline Tuple_text<float, long>(float a, long __d1)
: Tuple_text<long>(__d1)
, val{a}
{
}
};
template<>
struct Tuple_text<char, float, long> : public Tuple_text<float, long>
{
char val;
using base = Tuple_text<float, long>;
inline Tuple_text();
template<>
inline Tuple_text<char, float, long>(char a, float __d1, long __d2)
: Tuple_text<float, long>(__d1, __d2)
, val{a}
{
}
};
template<>
struct Tuple_text<int, char, float, long> : public Tuple_text<char, float, long>
{
int val;
using base = Tuple_text<char, float, long>;
inline Tuple_text();
template<>
inline Tuple_text<int, char, float, long>(int a, char __d1, float __d2, long __d3)
: Tuple_text<char, float, long>(__d1, __d2, __d3)
, val{a}
{
}
};
int main()
{
Tuple_text<int, char, float, long> c = Tuple_text<int, char, float, long>(100, 'o', 6.0F, 500L);
return 0;
}
注意main 函数里
我们看到这是一系列的继承关系
我们去vs 看看内存布局
我们看到 Tuple_text<int, char, float, long> 类里面有所有的 val
那我们应该怎么拿到呢?
Tuple_text<int, char, float, long> 的 val 很简单
但是 继承的 父类 Tuple_text<char, float, long> 的 val
怎么拿呢?
我们要是能转换为 父类对象就好了
using base = Tuple_text<Ty2...>;
base& get()
{
return *this;
}
这样是不是就能拿到父类对象了?
Tuple_text<int, char, float, long> : Tuple_text<char, float, long> :
Tuple_text<float, long> : Tuple_text<long> : Tuple_text<>
每一级的 base 都是本级继承的父类
c.val c.get().val c.get().get().val c.get().get().get().val
Tuple_text<> 这个是我们自己特化的类
是一个空的 所以继承链到此终结
模板编程是面向编译器的
很强大 但是也很难以解读
模板的玩法不只这些 玩法很多很多 看大家积累了 我也需要积累