Ru-Brd |
The nested structure of recursive duos is convenient to apply template metaprogramming techniques to them. However, for a human programmer it is more pleasing to have a flat interface to this structure. To obtain this, we can define a recursive Tuple template with many parameters and have it be a derivation from a recursive duo type of appropriate size. We show the code here for tuples up to five fields, but it is not significantly harder to provide for a dozen fields or so. You can find the code in tuples/tuple1.hpp . To allow for tuples of varying sizes, we have unused type parameters that default to a null type, NullT , which we define as a placeholder for that purpose. We use NullT rather than void because we will create parameters of that type ( void cannot be a parameter type): // type that represents unused type parameters class NullT { }; Tuple is defined as a template that derives from a Duo having one more type parameter with NullT defined: // Tuple<> in general derives from Tuple<> with one more NullT 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; // constructors: Tuple() {} Tuple(TypeOp<P1>::RefConstT a1, TypeOp<P2>::RefConstT a2, TypeOp<P3>::RefConstT a3 = NullT(), TypeOp<P4>::RefConstT a4 = NullT(), TypeOp<P5>::RefConstT a5 = NullT()) : BaseT(a1, Tuple<P2,P3,P4,P5,NullT>(a2,a3,a4,a5)) { } }; Note the shifting pattern when passing the parameters to the recursive step. Because we derive from a base type that defines member types T1 and T2 , we used template parameter names of the form P n instead of the usual T n . [2]
We need a partial specialization to end this recursion with the derivation from a nonrecursive duo: // specialization to end deriving recursion 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, TypeOp<P2>::RefConstT a2, TypeOp<NullT>::RefConstT = NullT(), TypeOp<NullT>::RefConstT = NullT(), TypeOp<NullT>::RefConstT = NullT()) : BaseT(a1, a2) { } }; A declaration such as Tuple<bool,int,float,double> t4(true,42,13,1.95583); ends up in the hierarchy shown in Figure 21.1. Figure 21.1. Type of Tuple<bool,int,float,double>
The other specialization takes care of the case when the tuple is really a singleton: // specialization for 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, TypeOp<NullT>::RefConstT = NullT(), TypeOp<NullT>::RefConstT = NullT(), TypeOp<NullT>::RefConstT = NullT(), TypeOp<NullT>::RefConstT = NullT()) : BaseT(a1) { } }; Finally, it is natural to desire functions like make_duo() in Section 21.1 on page 396 to deduce the template parameters automatically. Unfortunately, a different function template declaration is needed for each tuple size that must be supported because function templates cannot have default template arguments, [3] nor are their default function call arguments considered in the template parameter deduction process. The functions are defined as follows :
// convenience function for 1 argument template <typename T1> inline Tuple<T1> make_tuple(T1 const &a1) { return Tuple<T1>(a1); } // convenience function for 2 arguments template <typename T1, typename T2> inline Tuple<T1,T2> make_tuple(T1 const &a1, T2 const &a2) { return Tuple<T1,T2>(a1,a2); } // convenience function for 3 arguments template <typename T1, typename T2, typename T3> inline Tuple<T1,T2,T3> make_tuple(T1 const &a1, T2 const &a2, T3 const &a3) { return Tuple<T1,T2,T3>(a1,a2,a3); } // convenience function for 4 arguments template <typename T1, typename T2, typename T3, typename T4> inline Tuple<T1,T2,T3,T4> make_tuple(T1 const &a1, T2 const &a2, T3 const &a3, T4 const &a4) { return Tuple<T1,T2,T3,T4>(a1,a2,a3,a4); } // convenience function for 5 arguments template <typename T1, typename T2, typename T3, typename T4, typename T5> inline Tuple<T1,T2,T3,T4,T5> make_tuple(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); } The following program shows how to use Tuple s: // tuples/tuple1.cpp #include "tuple1.hpp" #include <iostream> int main() { // create and use tuple with only one field Tuple<int> t1; val<1>(t1) += 42; std::cout << t1.v1() << std::endl; // create and use duo Tuple<bool,int> t2; std::cout << val<1>(t2) << ", "; std::cout << t2.v1() << std::endl; // create and use 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; // create and use 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; } An industrial-strength implementation would complete the code we presented so far with various extensions. For example, we could define assignment operator templates to facilitate tuple conversions; otherwise , the types have to match exactly: Tuple<bool,int,float> t3; t3 = make_tuple(false, 23, 13.13); // ERROR: 13.13 has type double |
Ru-Brd |