21.3 Tuple Construction

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]

[2] A very curious lookup rule in C++ prefers names inherited from nondependent base classes over template parameter names. This should not be a problem in this case because the base class is dependent, but some compilers still get this wrong at the time of this writing.

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>

graphics/21fig01.gif

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 :

[3] A revision of the C++ standard will most likely remove this limitation (see Section 13.3 on page 207).

  // 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


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net