C++ template —— tuple(十三)
本系列博文中我们使用同类容器(如数组类型)来阐述模板的强大威力,同时,C/C++还具有包含异类对象的能力。这里的异类指的是类型不同,或者结构不同。tuple就是这样的一个类模板,它能够用于聚集不同类型的对象,本篇博文旨在介绍可以聚集任意个数的成员对象。
------------------------------------------------------------------------------------------------------------
21.1 duo
自定义的duo的目的是把两个对象聚集到一个单一类型(类似标准库的std::pair)。
template <typename T1, typename T2> struct Duo { //add2: 提供域类型的访问 typedef T1 Type1; // 第1个域的类型 typedef T2 Type2; // 第2个域的类型 enum { N = 2 }; // 域的个数 // end add2 T1 v1; // 第1个域的值 T2 v2; // 第2个域的值 //add1: 并且给它添加两个构造函数 Duo() : v1(), v2() { } Duo(T1 const&a, T2 const& b) : v1(a), v2(b) { } // end add1 }; // 辅助函数 template <typename T1, typename T2> inline Duo<T1, T2> make_duo(T1 const& a, T2 const& b) { return Duo<T1, T2>(a, b); } // 对于某些需要判断返回结果是否有效的函数而言会很有用 Duo<bool, X> result = foo(); if (result.v1) { // 结果是有效的,返回值是result.v2 ... } // 创建和初始化Duo也非常简单 make_duo(true, 42);
上面实现的duo已经很接近std::pair了,但还有一些不同之处,如在构造函数中我们没有提供用于隐式类型转换的成员模板初始化函数;没有提供比较运算符等;基于这些区别,我们下面给出一个更强大清晰的实现:
// tuples/duo1.hpp #ifndef DUO_HPP #define DUO_HPP template <typename T1, typename T2> class Duo { public: // 提供域类型的访问 typedef T1 Type1; // 第1个域的类型 typedef T2 Type2; // 第2个域的类型 enum { N = 2 }; // 域的个数 private: T1 value1; // 第1个域的值 T2 value2; // 第2个域的值 public: // 并且给它添加两个构造函数 Duo() : value1(), value2() { } Duo(T1 const&a, T2 const& b) : value1(a), value2(b) { } // 用于在构造期间,进行隐式的类型转换 template <typename U1, typename U2> Duo(Duo<U1, U2> const& d) : value1(d.v1()), value2(d.v2()) { } // 用于在赋值期间,进行隐式的类型转换 template<typename U1, typename U2> Duo<T1, T2>& operator= (Duo<U1, U2> const& d){ value1 = d.value1; value2 = d.value2; return *this; } // 用于访问域的函数(域访问函数) T1& v1(){ return value1; } T1 const& v1() const { return value1; } T2& v2(){ return value2; } T2 const& v2() const { return value2; } }; // 比较运算符(允许混合类型): template<typename T1, typename T2, typename U1, typename U2> inline bool operator==(Duo<T1, T2> const& d1, Duo<U1, U2> const& d2) { return d1.v1() == d2.v1() && d1.v2() == d2.v2(); } template<typename T1, typename T2, typename U1, typename U2> inline bool operator!=(Duo<T1, T2> const& d1, Duo<U1, U2> const& d2) { return !(d1 == d2); } // 针对创建和初始化的辅助函数 template<typename T1, typename T2> inline Duo<T1, T2> make_duo(T1 const& a, T2 const& b) { return Duo<T1, T2>(a, b); } #endif // DUO_HPP // tuples/duo1.cpp #include "duo1.hpp" Duo<float, int> foo() { // Duo<int, int> 到返回类型Duo<float, int>的隐式转型 return make_duo(42, 42); } int main() { // Duo<float, int> 到返回类型Duo<int, double>的隐式转型 if (foo() == make_duo(42, 42.0)) { ... } }
21.2 可递归duo
21.2.1 域的个数
书中在本节提供了一个简单的可递归duo:
// tuples/duo2.hpp template <typename A, typename B, typename C> class Duo<A, Duo<B, C> > { public: typedef A T1; // 第1个域的类型 typedef Duo<B, C> T2; // 第2个域的类型 enum { N = Duo<B, C>::N + 1 }; // 域的个数 private: T1 value1; // 第1个域的值 T2 value2; // 第2个域的值 public: // 其他的公共成员都不需要改变 ..... }; // tuples/duo6.hpp // 相应的递归出口 template <typename A> struct Duo<A, void> { public: typedef A T1; // 第1个域的类型 typedef void T2; // 第2个域的类型 enum { N = 1 }; // 域的个数 private: T1 value1; // 第1个域的值 public: // 构造函数 Duo() : value1() {} Duo(T1 const& a) : value1(a) {} // 域访问函数 T1& v1(){ return value1; } T1 const& v1() const{ return value1; } void v2() {} void v2() const{} .... };
21.2.2 域的类型
用于获取duo的第N个域的类型(即T)的基本模板
// (1)对于non-Duo(非duo)而言,结果类型为void template <int N, typename T> class DuoT { public: typedef void ResultT; // 一般情况下,结构类型是void }; // (2)对于非递归的duo,定义两个简单的局部特化,用于获取每个域的类型 // 针对普通duo第1个域的特化 template <typename A, typename B> class DuoT<1, Duo<A, B> > { public: typedef A ResultT; }; // 针对普通duo第2个域的特化 template <typename A, typename B> class DuoT<2, Duo<A, B> > { public: typedef B ResultT; }; // (3)可递归duo的第N个域的类型:一般情况下,它等于第2个域的第N-1个域的类型 // 针对可递归duo第N个域的类型的特化 template <int N, typename A, typename B, typename C> class DuoT<N, Duo<A, Duo<B, C> > > { public: typedef typename DuoT<N-1, Duo<B, C> >::ResultT ResultT; }; // (4)另外,针对可递归duo第1个(域的)类型的特化如下 // 针对可递归duo第1个域的特化 template <typename A, typename B, typename C> class DuoT<1, Duo<A, Duo<B, C> > > { public: typedef A ResultT; }; // 针对可递归duo第2个(域的)类型的特化如下 // 针对可递归duo第2个域的特化 template <typename A, typename B, typename C> class DuoT<2, Duo<A, Duo<B, C> > > { public: typedef B ResultT; };
21.2.3 域的值
在一个可递归duo中,就操作而言,抽取第N个值与抽取第N个类型是类似的,只是抽取第N个值要稍微复杂一些。为了能够抽取第N个值,我们需要实现一个形为val<N>(duo)的接口。但是在实现该接口的过程中,我们需要先实现一个辅助类模板DuoValue,因为只有类模板才能够被局部特化(函数模板现在还不可以),而局部特化能够帮助我们高效地抽取第N个值。如下:
// tuples/duo5.hpp #include "typeop.hpp" // 返回变量duo的第N个值 template <int N, typename A, typename B> inline typename TypeOp<typename DuoT<N, Duo<A, B> >::ResultT>::RefT val(Duo<A, B>& d) { return DuoValue<N, Duo<A, B> >::get(d); } // 返回常量duo的第N个值 template <int N, typename A, typename B> inline typename TypeOp<typename DuoT<N, Duo<A, B> >::ResultT>::RefConstT val(Duo<A, B> const& d) { return DuoValue<N, Duo<A, B> >::get(d); }
下面是DuoValue的一个完整实现
// tuples/duo4.hpp #include "typeop.hpp" //基本模板,针对(duo)T的第N个值 template <int N, typename N> class DuoValue { public: static void get(T&) { } // 一般情况下,并不返回值 static void get(T const&) { } }; // 针对普通duo的第N个域的特化 template <int N, typename N> class DuoValue<1, Duo<A, B> > { public: static A& get(Duo<A, B> & d) { return d.v1(); } static A const& get(Duo<A, B> const&) { return d.v1(); } }; // 针对普通duo第2个域的特化 template <typename A, typename B> class DuoValue<2, Duo<A, B> > { public: static B& get(Duo<A, B> &d){ return d.v2(); } static B const& get(Duo<A, B> const &d){ return d.v2(); } }; // 针对可递归duo的第N个值的特化 template <int N, typename A, typename B, typename C> struct DuoValue<N, Duo<A, Duo<B, C> > > { static typename TypeOp<typename DuoT<N-1, Duo<B, C> >::ResultT>::RefT get(Duo<A, Duo<B, C> > &d){ return DuoValue<N-1, Duo<B, C> >::get(d.v2()); } static typename TypeOp<typename DuoT<N-1, Duo<B, C> >::ResultT>::RefConstT get(Duo<A, Duo<B, C> > const &d){ return DuoValue<N-1, Duo<B, C> >::get(d.v2()); } }; // 针对可递归duo的第1个域的特化 template <typename A, typename B, typename C> class DuoValue<1, Duo<A, Duo<B, C> > { public: static A& get(Duo<A, Duo<B, C> > &d){ return d.v1(); } static A const& get(Duo<A, Duo<B, C> > const &d){ return d.v1(); } }; // 针对可递归duo的第2个域的特化 template <typename A, typename B, typename C> class DuoValue<2, Duo<A, Duo<B, C> > > { public: static B& get(Duo<A, Duo<B, C> > &d){ return d.v2().v1(); } static B const& get(Duo<A, Duo<B, C> > const &d){ return d.v2().v1(); } };
下面程序给出如何使用上面的duo:
// tuples/duo5.cpp #include "duo1.hpp" #include "duo2.hpp" #include "duo3.hpp" #include "duo4.hpp" #include "duo5.hpp" #include <iostream> int main() { // 创建和使用一个简单的duo Duo<bool, int> d; std::cout << d.v1() << std::endl; std::cout << val<1>(d) << std::endl; // 创建和使用triple Duo<bool, Duo<int, float> > t; val<1>(t) = true; val<2>(t) = 42; val<3>(t) = 0.2; std::cout << val<1>(t) << std::endl; std::cout << val<2>(t) << std::endl; std::cout << val<3>(t) << std::endl; }
例如,调用:
val<3>(t)
最后将会扩展为:
t.v2().v2()
21.3 tuple构造
上一节我们了解到可递归duo的嵌套结构有助于展现metaprogramming技术的应用,现在我们为把该结构封装成一个简单接口,从而可以在日常工作中使用这种结构。为了实现这种接口,我们可以定义一个含有多个参数的可递归tuple模板,并让它派生自一个可递归duo类型,其中该duo类型的域个数是有限制的(假设最多5个域)。
为了使tuple的大小(即域个数)是可变的,我们声明了一些无用的类型参数,它们缺省值是一个null类型;在此,我们特地定义了一个NullT类型,用于代表这种null类型。之所以使用NullT,而不使用void,是因为我们需要创建该类型(即NullT)的参数,而void是不能作为参数类型的:
// 用于代表无用类型参数的类型 class NullT { };
接下来,我们把tuple定义为一个模板,它派生自duo,而且该duo至少具有一个定义为NullT的类型参数:
// 一般情况下,Tuple<>都创建自“至少含有一个NullT的另一个Tuple<>” template <typename P1, typename P2 = NullT, typename P3 = NullT, typename P4 = NullT, typename P5 = NullT> class Tuple : public Duo<P1, typename Tuple<P2, P3, P4, P5, NullT>::BaseT> { public: typedef Duo<P1, typename Tuple<P2, P3, P4, P5, NullT>::BaseT> BaseT; // 构造函数 Tuple() { } Tuple(TypeOp<P1>::RefConstT a1, Tuple(TypeOp<P2>::RefConstT a2, Tuple(TypeOp<P3>::RefConstT a3 = NullT(), Tuple(TypeOp<P4>::RefConstT a4 = NullT(), Tuple(TypeOp<P5>::RefConstT a5 = NullT()) : BaseT(a1, Tuple<P2, P3, P4, P5, NullT>(a2, a3, a4, a5)){ // 递归减少参数个数 } }; // 用于终止递归的特化 template <typename P1, typename P2> class Tuple<P1, P2, NullT, NullT, NullT> : public Duo<P1, P2> { public: typedef Duo<P1, P2> BaseT; Tuple() { } Tuple(TypeOp<P1>::RefConstT a1, Tuple(TypeOp<P2>::RefConstT a2, Tuple(TypeOp<NullT>::RefConstT = NullT(), Tuple(TypeOp<NullT>::RefConstT = NullT(), Tuple(TypeOp<NullT>::RefConstT = NullT()) : BaseT(a1, a2){ } };
于是,有一个如下的声明:
Tuple<bool, int, float, double> t4(true, 42, 13, 1.95583);
最后的层次体系如下图21.1所示:
而其他的特化将会考虑tuple是一个singleton(即只具有一个域)的情形:
// 针对singletons的特化 template <typename P1> class Tuple<P1, NullT, NullT, NullT, NullT> : public Duo<P1, void> { public: typedef Duo<P1, void> BaseT; Tuple() { } Tuple(TypeOp<P1>::RefConstT a1, Tuple(TypeOp<NullT>::RefConstT = NullT(), Tuple(TypeOp<NullT>::RefConstT = NullT(), Tuple(TypeOp<NullT>::RefConstT = NullT(), Tuple(TypeOp<NullT>::RefConstT = NullT()) : BaseT(a1){ } };
最后,我们定义类似make_duo()的辅助函数,对每种不同大小的tuple,都需要声明一个不同的函数模板make_duo(),因为函数模板不能含有缺省模板实参,而且在模板参数的演绎过程中,也不会考虑缺省的函数调用实参:
// 针对一个实参的辅助函数 template <typename T1> inline Tuple<T1> make_duo(T1 const& a1) { return Tuple<T1>(a1); } // 针对两个实参的辅助函数 template <typename T1, typename T2> inline Tuple<T1, T2> make_duo(T1 const& a1, T2 const& a2) { return Tuple<T1, T2>(a1, a2); } // 针对3个实参的辅助函数 template <typename T1, typename T2, typename T3> inline Tuple<T1, T2, T3> make_duo(T1 const& a1, T2 const& a2, T3 const& a3) { return Tuple<T1, T2, T3>(a1, a2, a3); } // 针对4个实参的辅助函数 template <typename T1, typename T2, typename T3, typename T4> inline Tuple<T1, T2, T3, T4> make_duo(T1 const& a1, T2 const& a2, T3 const& a3, T4 const& a4) { return Tuple<T1, T2, T3, T4>(a1, a2, a3, a4); } // 针对5个实参的辅助函数 template <typename T1, typename T2, typename T3, typename T4, typename T5> inline Tuple<T1, T2, T3, T4, T5> make_duo(T1 const& a1, T2 const& a2, T3 const& a3, T4 const& a4, T5 const& a5) { return Tuple<T1, T2, T3, T4, T5>(a1, a2, a3, a4, a5); }
下面的程序给出如何使用该tuple:
// tuples/tuple1.cpp #include "tuple1.hpp" #include <iostream> int main() { // 创建和使用只具有1个域的tuple Tuple<int> t1; val<1>(t1) += 42; std::cout << t1.v1() << std::endl; // 创建和使用duo Tuple<bool, int> 42; std::cout << val<1>(t2) << ", " ; std::cout << t2.v1() << std::endl; // 创建和使用triple Tuple<bool, int, double> t3; val<1>(t3) = true; val<2>(t3) = 42; val<3>(t3) = 0.2; std::cout << val<1>(t3) << ", "; std::cout << val<2>(t3) << ", "; std::cout << val<3>(t3) << std::endl; t3 = make_tuple(false, 23, 13.13); std::cout << val<1>(t3) << ", "; std::cout << val<2>(t3) << ", "; std::cout << val<3>(t3) << std::endl; // 创建和使用quadruple Tuple<bool, int, float, double> t4(true, 42, 13, 1.95583); std::cout << val<4>(t4) << std::endl; std::cout << t4.v2().v2().v2() << std::endl; }