Solution

I l @ ve RuBoard

graphics/bulb.gif

Templates provide C++'s most powerful form of genericity. They allow you to write generic code that works with many kinds of unrelated objects ”for example, strings that contain various kinds of characters , containers that can hold arbitrary types of objects, and algorithms that can operate on arbitrary types of sequences.

1. What is template specialization? Give an example.

Template specialization lets templates deal with special cases. Sometimes, a generic algorithm can work much more efficiently for a certain kind of sequence (for example, when given random-access iterators), so it makes sense to specialize it for that case while using the slower but more generic approach for all other cases. Performance is a common reason to specialize, but it's not the only one. For example, you might also specialize a template to work with certain objects that don't conform to the normal interface expected by the generic template.

These special cases can be handled using two forms of template specialization: explicit specialization and partial specialization.

Explicit Specialization

Explicit specialization lets you write a specific implementation for a particular combination of template parameters. For example, consider the following function template:

 template<typename T> void sort( Array<T>& v ) { /*...*/ }; 

If we have a faster (or other specialized) way we want to deal with Array s of char* 's, we could explicitly specialize sort() for that case:

 template<> void sort<char*>( Array<char*>& ); 

The compiler will then choose the most appropriate template. For example:

 Array<int>   ai; Array<char*> apc; sort( ai );       // calls sort<int> sort( apc );      // calls specialized sort<char*> 

Partial Specialization

2. What is partial specialization? Give an example.

For class templates only, you can define partial specializations that don't have to fix all the primary (unspecialized) class template's parameters.

Here is an example from the C++ standard [C++98], in 14.5.4 [temp.class.spec]. The first template is the primary class template:

 template<typename T1, typename T2, int I> class A             { };             // #1 

We can specialize this for the case when T2 is a T1* :

 template<typename T, int I> class A<T, T*, I>   { };             // #2 

Or for the case when T1 is any pointer:

 template<typename T1, typename T2, int I> class A<T1*, T2, I> { };             // #3 

Or for the case when T1 is int and T2 is any pointer and I is 5:

 template<typename T> class A<int, T*, 5> { };             // #4 

Or for the case when T2 is any pointer:

 template<typename T1, typename T2, int I> class A<T1, T2*, I> { };             // #5 

Declarations 2 through 5 declare partial specializations of the primary template. The compiler will then choose the appropriate template. From [C++98] section 14.5.4.1 comes the following:

 A<int, int, 1>   a1;  // uses #1 A<int, int*, 1>  a2;  // uses #2, T is int,                       //          I is 1 A<int, char*, 5> a3;  // uses #4, T is char A<int, char*, 1> a4;  // uses #5, T1 is int,                       //          T2 is char,                       //          I is 1 A<int*, int*, 2> a5;  // ambiguous:                       // matches #3 and #5 

Function Template Overloading

Now let's consider function template overloading. It isn't the same as specialization, but it's related to it.

C++ lets you overload functions, yet makes sure the right one is called:

 int  f( int ); long f( double ); int    i; double d; f( i );   // calls f(int) f( d );   // calls f(double) 

Similarly, you can also overload function templates, which brings us to the final question:

3. Consider the following declarations:

  template<typename T1, typename T2>   void g( T1, T2 );                       // 1   template<typename T> void g( T );       // 2   template<typename T> void g( T, T );    // 3   template<typename T> void g( T* );      // 4   template<typename T> void g( T*, T );   // 5   template<typename T> void g( T, T* );   // 6   template<typename T> void g( int, T* ); // 7   template<> void g<int>( int );          // 8   void g( int, double );                  // 9   void g( int );                          // 10  

First, let's simplify things a little by noticing that there are two groups of overloaded g() 's here: those that take a single parameter and those that take two parameters.

 template<typename T1, typename T2> void g( T1, T2 );                       // 1 template<typename T> void g( T, T );    // 3 template<typename T> void g( T*, T );   // 5 template<typename T> void g( T, T* );   // 6 template<typename T> void g( int, T* ); // 7 void g( int, double );                  // 9 template<typename T> void g( T );       // 2 template<typename T> void g( T* );      // 4 template<> void g<int>( int );          // 8 void g( int );                          // 10 

Note that I deliberately didn't muddy the waters by including an overload with two parameters where the second parameter had a default. Had there been such a function, then for the purposes of determining the correct ordering, it should be considered in both lists ”once as a single-parameter function (using the default), and once as a two-parameter function (not using the default).

Now let's consider each of the calls in turn :

Which of the above functions are invoked by each of the following? Identify the template parameter types, where appropriate.

  int             i;   double          d;   float           f;   complex<double> c;   g( i );         // a  
  1. This calls #10, because it's an exact match for #10 and such non-templates are always preferred over templates (see 13.3.3).

      g<int>( i );    // b  
  2. This calls #8, because g<int>() is being explicitly requested .

      g( i, i );      // c  
  3. This calls #3 ( T is int ), because that is the best match.

      g( c );         // d  
  4. This calls #2 ( T is complex<double> ), because no other g() can match.

      g( i, f );      // e  
  5. This calls #1 ( T1 is int , T2 is float ).

    You might think that #9 is very close ”and it is ”but a nontemplate function is preferred only if it is an exact match. In this case, #9 is only close, not exact.

      g( i, d );      // f  
  6. This one does call #9, because this time #9 is an exact match and the nontemplate is preferred.

      g( c, &c );     // g  
  7. This calls #6 ( T is complex<double> ), because #6 is the closest overload. Template #6 provides an overload of g() , where the second parameter is a pointer to the same type as the first parameter.

      g( i, &d );     // h  
  8. This calls #7 ( T is double ), because #7 is the closest overload.

      g( &d, d );     // i  
  9. This calls #5 ( T is double ). #5 provides an overload of g() , where the first parameter is a pointer to the same type as the second parameter.

    Only a few more to go:

      g( &d );        // j  
  10. Clearly (by now), we're asking for #4 ( T is double ).

      g( d, &i );     // k  
  11. Several other overloads are close, but only #1 fills the bill here ( T1 is double , T2 is int* ).

    And finally:

      g( &i, &i );    // l  
  12. This calls #3 ( T is int* ), which is the closest overload even though some of the others explicitly mention a pointer parameter.

The good news is that modern compilers generally have good template support so that you can make use of features like the above more reliably and portably than in the past.

The (potential) bad news is that if you got all the answers right, you may know the rules better than your current compiler does.

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

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