C++中的数组类型和容器类型都是存放同类型对象的,有一个标准库类型std::pair可以存放两个异类对象。借助模板,我们可以开发出存放异类对象的类型。
1、二元组类型: Duo<T1,T2>。存放两个异类对象的元组类型,类似于std::pair。设置和获取各个域的操作直接为Duo<T1,T2>的成员函数。make_duo函数用来方便地创建二元组对象,类似于make_pair。
有几点要注意:
(1)我们用typedef定义了所存放的两个类型,这样就可以方便地访问两个类型。
(2)我们提供了针对兼容类型的拷贝构造函数和赋值运算符,从而可进行隐式类型转换。
(3)访问域的函数有非const版本和const版本。若函数返回非const引用,可以修改对象的value1成员,因此函数也必须是非const的,以表示它可以修改对象的值。
(4)比较运算符允许对混合类型进行比较。make_duo是包装函数,用来方便地创建和初始化二元组。
2、一般的元组类型: 可递归Duo<A, Duo<B,C> >类型表示一般的元组类,它使用模板元编程技术通过对Duo进行模板递归来实现。DuoT<N,T>用来获取元组类T中第N个域的类型,DuoValue<N,T>用来设置或获取元组类T中第N个域的值,它们也都使用了模板元编程技术来实现,用了很多模板特化来结束递归。val<N>(d)用来方便地设置和获取元组d的第N个域的值。
要注意的地方:
(1)非const版本的那个静态get(d)函数由于返回的是非const引用,通过这个引用可获取d中相应域的值,也可设置d中相应域的值,因此参数d也必须是非const的(即变量型的),否则如果d是const的,则不能为d中的域设置新值,另一方面它会使d.v1()会返回const引用,从而get(d)不能返回非const引用。d为const的那个get(d)函数返回const引用,只能用于获取相应域的值,但函数本身不能是const的,因为static成员函数不能同时又是const的。
(2)在特化版本DuoValue<N, Duo<A, Duo<B,C> > >中,我们用到了类型萃取技术中开发的TypeOp模板,这可确保返回类型是相应域类型的非const引用或const引用。
(3)val<N>(d)要提供两个版本,一个接受非const的引用参数d,可设置和获取d的第N个域的值,一个接受const引用参数d,只能获取d的第N个域的值,注意基于“引用型参数是否是const的”可以构成重载关系。
(4)元组类型Duo<A, Duo<B,C> >虽然理论上可以存放任意多个异类元素,但实际的编译器对模板嵌套深度是有限制的,因此这个元组类在实际应用时并不能存放太多个异类元素。
3、元素个数有限的元组类: 由于模板嵌套深度在实际中有限制,因此有时我们更愿意开发一个接口简单的、元素个数有限的元组类。Tuple<P1,P2,P3,P4,P5>就是这样一个元组类,它可存放1-5个域。也是用模板递归来实现的,Tuple继承自前面的可递归Duo类型,其中该Duo类型的域个数有限。
有几点要注意:
(1)由于Tuple的域个数是可变的,因此我们声明了一个无用类型NullT来作模板参数的默认实参。这里并没有使用void类型,因为我们需要在构造函数中创建该类型的对象来作为函数形参的缺省值,而void类型不能创建对象。
(2)用typedef定义BaseT来表示它继承的可递归Duo类型。
(3)make_tuple用来方便地创建tuple元组。由于函数模板不能含有缺省模板实参,因此我们不能只写一个含有5个模板参数(通过给一些模板参数提供缺省实参)的make_tuple。如果想通过给函数的5个形参提供缺省调用实参来达到只编写一个make_tuple的愿望,这也不行,因为在演绎模板参数时,不会考虑缺省调用实参,这样就会导致使用缺省调用实参的模板参数演绎不出来,从而必须显式指定,使用起来不方便。总之,我们必须针对不同的域个数来定义相应的make_tuple。