CIS-255 Home http://www.c-jump.com/bcc/c255c/c255syllabus.htm
How could we get the compiler to write code for us?
Recall the IntStack class.
How do we produce DoubleStack?
Global search/replace int with double?
Why not build a
class prototype with a dummy typename parameter,
then use that prototype to construct needed implementations.
Using templates, we'll do the first part, the compiler will do the second part.
Stack< int > stack_i;
Stack< double > stack_d;
Stack< Stack < Rational > > stack_r; // be careful about spacing!
stack_i.push( 10 );
stack_d.push( 3.14 );
stack_d.push( stack_i.pop() );
Rational rat( 4, 5 );
stack_i.push( rat ); // type safety??
|
|
Places to use the template parameter:
template < typename ValueT > class Stack { public: // Constructor name explicit Stack< ValueT >( int initial_size ); // Arguments of the class type Stack< ValueT >( Stack< ValueT > const& other ); // Other function arguments void push( ValueT const& item ); private: // Types of members ValueT* m_ptr_data; };//class Stack
Templates need to be visible where they are used, so it's usual to put them entirely in the header file.
Compilers are required to instantiate only functions that will actually be used!!
Therefore good coverage by test driver program is really crucial.
Clients of templates won't want to know or care that they are using a template,
and they shouldn't need to.
A useful design question for container-style templates:
can this container be used on itself?
A template makes demands on the types that user wants to use in place of template paremeters such as ValueT.
If demands aren't satisfied, it's an error.
So in a way, a template is a client of the typename ValueT, without knowing what ValueT is yet.
Native C++ types allow lots of operations, so they should almost always be considered first.
Templates are another reason to have overloaded operators,
and to have them behave like the native types do.
Sometimes, you want to construct an object of type ValueT with default constructor behavior:
template < typename ValueT >
ValueT Stack< ValueT >::pop()
{
if ( is_empty() ) {
cerr << "Pop failed, stack is empty" << endl;
return ValueT();
}
return m_ptr_data[ --m_top ];
}
Happily, this works fine when ValueT is a native C++ type!
|
|
Since we can build templates for classes, why not have templates for functions?
// return bigger int int max_int( int a, int b ) { return ( a > b ) ? a : b; } // return bigger ValueT template< typename ValueT > ValueT const& max_val( ValueT const& a, ValueT const& b ) { return ( a > b ) ? a : b; }
Type ValueT will be deduced from the actual call parameters.
Here, max_val( ) requires availability of operator> for ValueT:
#include <iostream> #include <string> // return bigger ValueT template< typename ValueT > ValueT const& max_val( ValueT const& a, ValueT const& b ) { return ( a > b ) ? a : b; } int main() { using namespace std; string str_a( "hello" ); string str_b( "bye" ); string bigger = max_val( str_a, str_b ); cout << bigger; return 0; } // Program prints "hello"
#include <iostream>
using namespace std;
// Forward declare class template
template < typename ValueT > class Container;
// Forward declare function template
template < typename ValueT > ostream& operator<<( ostream& os, Container< ValueT > const& v );
template < typename ValueT >
class Container {
public:
// This friend declaration is a template of ValueT
// function because of the extra <> after the name:
friend ostream& operator<< <>( ostream& os, Container< ValueT > const& v );
private:
ValueT m_data;
};
int main()
{
Container< int > vect_i;
cout << vect_i;
return 0;
}
template < typename ValueT > ostream& operator<<( ostream& os, Container< ValueT > const& v )
{
os << v.m_data;
return os;
}
If we left out the < > part, we would get a
Warning: friend declaration declares a non-template function if this is not what you intended, make sure the function template has already been declared and add <> after the function name here. Linker error: unresolved external symbol...
Inheritance allows code reuse, so do templates.
Inheritance implies an asymmetric relationship between classes.
template instantiations are unrelated.
Templates support compile-time polymorphism.
Inheritance can support only run-time polymorphism.
Again, it's usually clear which one to use.
Combinations are possible, and often used in modern code.
Implement a concrete version first.
Consider using something other than the type you are most interested in.
When you convert to a template, be sure to test using several different classes.
It's a good practice to try first to be a template user,
and our next topic is STL, the C++ standard library, or, as many programmers say,
the Standard Template Library.
Templates can have more than one argument.
Templates can have different kinds of arguments.
Non-class arguments must be supplied with constants:
template< typename ValueT, int SIZE >
class FixedSizeBuffer {
ValueT m_buffer[ SIZE ];
int m_size;
public:
FixedSizeBuffer() : m_size( SIZE ) {}
};//class FixedSizeBuffer
FixedSizeBuffer< char, 80 > line; // ValueT is char, SIZE is 80
FixedSizeBuffer< string, 10 > sb; // ValueT is string, SIZE is 10
Templates arguments can default.
As with function arguments, this is like overloading:
template < typename Type1, typename Type2 = double > class Point { public: // Constructor Point< Type1, Type2 >( Type1 x, Type2 y) : m_x( x ), m_y( y ) { } Type2 distance() { return static_cast< Type2 >( sqrt( m_x * m_x + m_y * m_y ) ); } private: Type1 m_x; Type2 m_y; };//class Point
Sometimes, you want certain template parameter types to be treated in a special way.
Usually, this is for efficiency.
As with function names, this is like overloading:
// General version comes first:
template < typename ValueT >
class Stack< ValueT > {
//...
};
// Specialization for char follows:
template < >
class Stack< char > {
//...
};
It's also possible to treat entire groups of types separately.
For example, you can treat all pointer-types differently compared to non-pointer types:
// Most general version goes first:
template < typename ValueT >
class Stack< ValueT > {
//...
};
// Specialization for pointer-types follows:
template < typename ValueT >
class Stack< ValueT* > {
//...
};
A cute template specialization trick: factorial calculation made at compile time:
// factorial.cpp template < int N > struct Factorial { enum { value = N * Factorial< N-1 >::value }; }; template < > struct Factorial< 1 > { enum { value = 1 }; }; // main.cpp #include <iostream> int main() { const int fact5 = Factorial< 5 >::value; std::cout << fact5 << '\n'; return 0; } // Program prints 120