元模板 笔记
对类型编写,由于c++不存在
if(type == xxx) {}
这种语法。
类型计算可以使用:
1,重载。
2,虚函数。继承。
3,c语言中利用 Union
查看代码
struct Variant { union { int x; float y; } data; uint32 typeId; }; Variant addFloatOrMulInt(Variant const* a, Variant const* b) { Variant ret; assert(a->typeId == b->typeId); if (a->typeId == TYPE_INT) { ret.x = a->x * b->x; } else { ret.y = a->y + b->y; } return ret; }
4,void*
查看代码
#define BIN_OP(type, a, op, b, result) (*(type *)(result)) = (*(type const *)(a)) op (*(type const*)(b)) void doDiv(void* out, void const* data0, void const* data1, DATA_TYPE type) { if(type == TYPE_INT) { BIN_OP(int, data0, *, data1, out); } else { BIN_OP(float, data0, +, data1, out); } }
5,C++中比如在 Boost.Any
的实现中,运用了 typeid
来查询类型信息。
6,和 typeid
同属于RTTI机制的 dynamic_cast
,也经常会用来做类型判别的工作。
查看代码
IAnimal* animal = GetAnimalFromSystem(); IDog* maybeDog = dynamic_cast<IDog*>(animal); if(maybeDog) { maybeDog->Wangwang(); } ICat* maybeCat = dynamic_cast<ICat*>(animal); if(maybeCat) { maybeCat->Moemoe(); }
用模板后
在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。
特化:
// 我们这个模板的基本形式是什么? template <typename T> class AddFloatOrMulInt; // 但是这个类,是给T是Int的时候用的,于是我们写作 class AddFloatOrMulInt<int> // 当然,这里编译是通不过的。 // 但是它又不是个普通类,而是类模板的一个特化(特例)。 // 所以前面要加模板关键字template, // 以及模板参数列表 template </* 这里要填什么? */> class AddFloatOrMulInt<int>; // 最后,模板参数列表里面填什么?因为原型的T已经被int取代了。所以这里就不能也不需要放任何额外的参数了。 // 所以这里放空。 template <> class AddFloatOrMulInt<int> { // ... 针对Int的实现 ... } // Bingo!
类型参数相当于函数的参数,里面的定义的成员值,相当于返回值。定义多个成员,意味着可以返回多个值了!!
//对于一般没匹配特例的,参数是T,类型函数返回值是 -2. template <typename T> class TypeToID { public: static int const NotID = -2; }; //对于float参数,返回值是1。注意取值时,可以自己写了 ID. 而上面用的是 NotID template <> class TypeToID<float> { public: static int const ID = 1; };
类模板和类模板的特化的作用,仅仅是指导编译器选择哪个编译,但是特化之间、特化和它原型的类模板之间,是分别独立实现的。所以如果多个特化、或者特化和对应的类模板有着类似的内容,很不好意思,你得写上若干遍了。
重载与模板
重载:
void doWork(int); void doWork(float); void doWork(int, int); void f() { doWork(0); doWork(0.5f); doWork(0, 0); }
对应模板
template <typename T> struct DoWork; // (0) 这是原型 相当于函数,即申明了 只有一个类型参数的 函数。 //上面相当于申明函数f(T), T是一个类型。不普通函数的值。那么f(int),f(float)都行。f(int,int)多了个参数,肯定不行了。 template <> struct DoWork<int> {}; // (1) 这是 int 类型的"重载" template <> struct DoWork<float> {}; // (2) 这是 float 类型的"重载" template <> struct DoWork<int, int> {}; // (3) 这是 int, int 类型的“重载” void f(){ DoWork<int> i; DoWork<float> f; DoWork<int, int> ii; }
所以DoWork<int, int>会导致编译错误,实参和原型不对应啊。因为原型里面只有一个参数 T。<int,int>是两个参数。
练习:
#include <memory> using std::unique_ptr; using std::shared_ptr; template <typename T, typename U> struct X ; // #0 原型有两个类型参数 // 所以下面的这些偏特化的实参列表 // 也需要两个类型参数对应 // 下面的这些偏特化的“小尾巴”也需要两个类型参数对应 template <typename T> struct X<T, T > {}; // 1 template <typename T> struct X<T*, T > {}; // 2 template <typename T> struct X<T, T* > {}; // 3 template <typename U> struct X<U, int> {}; // 4 template <typename U> struct X<U*, int> {}; // 5 template <typename U, typename T> struct X<U*, T* > {}; // 6 template <typename U, typename T> struct X<U, T* > {}; // 7 template <typename T> struct X<unique_ptr<T>, shared_ptr<T>>; // 8 // 以下特化,分别对应哪个偏特化的实例? // 此时偏特化中的T或U分别是什么类型? X<float*, int> v0; //5 第二个是整型,先看偏特化第二个参数是整型的。再看第一个是指针。 X<double*, int> v1; //5 X<double, double> v2; //1 第二个参数没有double的。再看两个参数是一个类型的。 X<float*, double*> v3; //6 两个参数不一样,但是都是指针的。 // X<float*, float*> v4; //两个参数都一样,并且是指针的。否则还得去指针。编译器不这么认为,报错!!! X<double, float*> v5; //7 X<int, double*> v6; //7 //X<int*, int> v7; X<double*, double> v8; //2
在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板形参,和原型的模板形参没有任何关系。
和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如<U, int>
中U
的声明,真正的模式,是由<U, int>
体现出来的。
这也是为什么在特化的时候,当所有类型都已经确定,我们就可以抛弃全部的模板参数,写出template <> struct X<int, float>
这样的形式:因为所有列表中所有参数都确定了,就不需要额外的形式参数了。
其次,作为一个模式匹配,偏特化的实参列表中展现出来的“样子”,就是它能被匹配的原因。比如,struct X<T, T>
中,要求模板的两个参数必须是相同的类型。而struct X<T, T*>
,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如X<float***, float****>
就能匹配上。当然,除了简单的指针、const
和volatile
修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的unique_ptr
和shared_ptr
。C++标准中指出下列模式都是可以被匹配的:
N3337, 14.8.2.5/8
令
T
是模板类型实参或者类型列表(如 int, float, double 这样的,TT
是template-template实参(参见6.2节),i
是模板的非类型参数(整数、指针等),则以下形式的形参都会参与匹配:
T
,cv-list T
,T*
,template-name <T>
,T&
,T&&
T [ integer-constant ]
type (T)
,T()
,T(T)
T type ::*
,type T::*
,T T::*
T (type ::*)()
,type (T::*)()
,type (type ::*)(T)
,type (T::*)(T)
,T (type ::*)(T)
,T (T::*)()
,T (T::*)(T)
type [i]
,template-name <i>
,TT<T>
,TT<i>
,TT<>
对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是<float*, float*>
,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。
其他的示例可以先自己推测一下, 再去编译器上尝试一番:goo.gl/9UVzje
。
非要用重载:
DoWork<int, void> i; DoWork<float, void> f; DoWork<int, int > ii;
这时,我们就能写出统一的模板原型:
template <typename T0, typename T1> struct DoWork;
继而偏特化/特化问题也解决了:
template <> struct DoWork<int, void> {}; // (1) 这是 int 类型的特化 template <> struct DoWork<float, void> {}; // (2) 这是 float 类型的特化 template <> struct DoWork<int, int> {}; // (3) 这是 int, int 类型的特化
用默认模板参数,解决不停的写void的问题
template <typename T0, typename T1 = void> struct DoWork; template <typename T> struct DoWork<T> {}; #0 template <> struct DoWork<int> {};#1 template <> struct DoWork<float> {};#2 template <> struct DoWork<int, int> {};#3 DoWork<int> i; #1 DoWork<float> f; #2 DoWork<double> d;#0 DoWork<int, int> ii;#3
终极用变长模板参数
template <typename... Ts, typename U> class X {}; // (1) error! template <typename... Ts> class Y {}; // (2) template <typename... Ts, typename U> class Y<U, Ts...> {}; // (3) template <typename... Ts, typename U> class Y<Ts..., U> {}; // (4) error!
为什么第(1)条语句会出错呢?(1)是模板原型,模板实例化时,要以它为基础和实例化时的类型实参相匹配。因为C++的模板是自左向右匹配的,所以不定长参数只能结尾。其他形式,无论写作Ts, U
,或者是Ts, V, Us,
,或者是V, Ts, Us
都是不可取的。(4) 也存在同样的问题。
但是,为什么(3)中, 模板参数和(1)相同,都是typename... Ts, typename U
,但是编译器却并没有报错呢?
答案在这一节的早些时候。(3)和(1)不同,它并不是模板的原型,它只是Y
的一个偏特化。回顾我们在之前所提到的,偏特化时,模板参数列表并不代表匹配顺序,它们只是为偏特化的模式提供的声明,也就是说,它们的匹配顺序,只是按照<U, Ts...>
来,而之前的参数只是告诉你Ts
是一个类型列表,而U
是一个类型,排名不分先后。
template <> class lazy_string_concat_helper<> template <typename... Strings> class lazy_string_concat_helper; template <typename LastString, typename... Strings> class lazy_string_concat_helper<LastString, Strings...>
lazy_string_concat_helper<std::string> 将被匹配成:lazy_string_concat_helper<std::string>
最具体的是匹配到了第3个:
template <typename LastString, typename... Strings>
class lazy_string_concat_helper<LastString,
Strings...>
最终的特例化是:
template <> class lazy_string_concat_helper<typename std::string >;
即特例化成:
template<>
class lazy_string_concat_helper<std::string >{
public:
inline lazy_string_concat_helper(std::basic_string<char> data, lazy_string_concat_helper<> tail)
: data{std::basic_string<char>(data)}
, tail{lazy_string_concat_helper<>(tail)}
{
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?